commit 573584854ea4ed5dbd8746da63aaf9cd4dd4e000 Author: nicolas.borges Date: Fri Nov 21 11:32:29 2025 -0300 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..362ad98 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +/target +src/.env +.env +log/ +.zip +evaluations \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e46adf --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/target +src/.env +.env +log/ +.zip +.vscode +evaluations +groupped +# Added by cargo +# +# already existing elements were commented out + +#/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3b54114 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4166 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[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 = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "argminmax" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f13d10a41ac8d2ec79ee34178d61e6f47a29c2edfe7ef1721c7383b0359e65" +dependencies = [ + "num-traits", +] + +[[package]] +name = "array-init-cursor" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed51fe0f224d1d4ea768be38c51f9f831dee9d05c163c11fba0b8c44387b1fc3" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atoi_simd" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a49e05797ca52e312a0c658938b7d00693ef037799ef7187678f212d7684cf" +dependencies = [ + "debug_unsafe", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "boxcar" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] + +[[package]] +name = "bzip2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "chumsky" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" +dependencies = [ + "hashbrown 0.14.5", + "stacker", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "comfy-table" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" +dependencies = [ + "crossterm", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "compact_str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "document-features", + "parking_lot", + "rustix", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "debug_unsafe" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85d3cef41d236720ed453e102153a53e4cc3d2fde848c0078a50cf249e8e3e5b" + +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "email-encoding" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6" +dependencies = [ + "base64", + "memchr", +] + +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "ethnum" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "libz-rs-sys", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs4" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", + "rayon", + "serde", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "hostname" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" +dependencies = [ + "cfg-if", + "libc", + "windows-link 0.1.1", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.5.10", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "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 = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipaddress" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957bb9f3645d6bb7f36df99d5105b4866aa79749819d7c176a170a27dc477cbf" +dependencies = [ + "lazy_static", + "libc", + "num", + "num-integer", + "num-traits", + "regex", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lettre" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f" +dependencies = [ + "base64", + "chumsky", + "email-encoding", + "email_address", + "fastrand", + "futures-util", + "hostname", + "httpdate", + "idna", + "mime", + "native-tls", + "nom", + "percent-encoding", + "quoted_printable", + "socket2 0.6.0", + "tokio", + "url", +] + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775bf80d5878ab7c2b1080b5351a48b2f737d9f6f8b383574eebcc22be0dfccb" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libz-rs-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" +dependencies = [ + "zlib-rs", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" +dependencies = [ + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "lzma-rust2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" +dependencies = [ + "crc", + "sha2", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +dependencies = [ + "libc", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "now" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89e9874397a1f0a52fc1f197a8effd9735223cb2390e9dcc83ac6cd02923d0" +dependencies = [ + "chrono", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[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-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[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-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "object_store" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c1be0c6c22ec0817cdc77d3842f721a17fd30ab6965001415b5402a74e6b740" +dependencies = [ + "async-trait", + "base64", + "bytes", + "chrono", + "form_urlencoded", + "futures", + "http", + "http-body-util", + "humantime", + "hyper", + "itertools", + "parking_lot", + "percent-encoding", + "quick-xml", + "rand", + "reqwest", + "ring", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror", + "tokio", + "tracing", + "url", + "walkdir", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piperun-bot" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "csv", + "dotenv", + "http", + "ipaddress", + "itertools", + "lettre", + "polars", + "regex", + "reqwest", + "serde", + "serde_json", + "walkdir", + "zip", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "planus" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3daf8e3d4b712abe1d690838f6e29fb76b76ea19589c4afa39ec30e12f62af71" +dependencies = [ + "array-init-cursor", + "hashbrown 0.15.3", +] + +[[package]] +name = "polars" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc9ea901050c1bb8747ee411bc7fbb390f3b399931e7484719512965132a248" +dependencies = [ + "getrandom 0.2.16", + "getrandom 0.3.3", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-io", + "polars-lazy", + "polars-ops", + "polars-parquet", + "polars-sql", + "polars-time", + "polars-utils", + "version_check", +] + +[[package]] +name = "polars-arrow" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d3fe43f8702cf7899ff3d516c2e5f7dc84ee6f6a3007e1a831a0ff87940704" +dependencies = [ + "atoi_simd", + "bitflags", + "bytemuck", + "chrono", + "chrono-tz", + "dyn-clone", + "either", + "ethnum", + "getrandom 0.2.16", + "getrandom 0.3.3", + "hashbrown 0.16.0", + "itoa", + "lz4", + "num-traits", + "polars-arrow-format", + "polars-error", + "polars-schema", + "polars-utils", + "serde", + "simdutf8", + "streaming-iterator", + "strum_macros", + "version_check", + "zstd", +] + +[[package]] +name = "polars-arrow-format" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a556ac0ee744e61e167f34c1eb0013ce740e0ee6cd8c158b2ec0b518f10e6675" +dependencies = [ + "planus", + "serde", +] + +[[package]] +name = "polars-compute" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29cc7497378dee3a002f117e0b4e16b7cbe6c8ed3da16a0229c89294af7c3bf" +dependencies = [ + "atoi_simd", + "bytemuck", + "chrono", + "either", + "fast-float2", + "hashbrown 0.16.0", + "itoa", + "num-traits", + "polars-arrow", + "polars-error", + "polars-utils", + "rand", + "ryu", + "serde", + "strength_reduce", + "strum_macros", + "version_check", +] + +[[package]] +name = "polars-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48409b7440cb1a4aa84953fe3a4189dfbfb300a3298266a92a37363476641e40" +dependencies = [ + "bitflags", + "boxcar", + "bytemuck", + "chrono", + "chrono-tz", + "comfy-table", + "either", + "hashbrown 0.16.0", + "indexmap", + "itoa", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-dtype", + "polars-error", + "polars-row", + "polars-schema", + "polars-utils", + "rand", + "rand_distr", + "rayon", + "regex", + "serde", + "serde_json", + "strum_macros", + "uuid", + "version_check", + "xxhash-rust", +] + +[[package]] +name = "polars-dtype" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7007e9e8b7b657cbd339b65246af7e87f5756ee9a860119b9424ddffd2aaf133" +dependencies = [ + "boxcar", + "hashbrown 0.16.0", + "polars-arrow", + "polars-error", + "polars-utils", + "serde", + "uuid", +] + +[[package]] +name = "polars-error" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a6be22566c89f6405f553bfdb7c8a6cb20ec51b35f3172de9a25fa3e252d85" +dependencies = [ + "object_store", + "parking_lot", + "polars-arrow-format", + "regex", + "signal-hook", + "simdutf8", +] + +[[package]] +name = "polars-expr" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6199a50d3e1afd0674fb009e340cbfb0010682b2387187a36328c00f3f2ca87b" +dependencies = [ + "bitflags", + "hashbrown 0.16.0", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-io", + "polars-ops", + "polars-plan", + "polars-row", + "polars-time", + "polars-utils", + "rand", + "rayon", + "recursive", + "regex", + "version_check", +] + +[[package]] +name = "polars-io" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be3714acdff87170141880a07f5d9233490d3bd5531c41898f6969d440feee11" +dependencies = [ + "async-trait", + "atoi_simd", + "blake3", + "bytes", + "chrono", + "fast-float2", + "fs4", + "futures", + "glob", + "hashbrown 0.16.0", + "home", + "itoa", + "memchr", + "memmap2", + "num-traits", + "object_store", + "percent-encoding", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-parquet", + "polars-schema", + "polars-time", + "polars-utils", + "rayon", + "regex", + "reqwest", + "ryu", + "serde", + "serde_json", + "simdutf8", + "tokio", +] + +[[package]] +name = "polars-lazy" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea136c360d03aafe56e0233495e30044ce43639b8b0360a4a38e840233f048a1" +dependencies = [ + "bitflags", + "chrono", + "either", + "memchr", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-ops", + "polars-plan", + "polars-stream", + "polars-time", + "polars-utils", + "rayon", + "version_check", +] + +[[package]] +name = "polars-mem-engine" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6e455ceb6e5aee7ed7d5c8944104e66992173e03a9c42f9670226318672249" +dependencies = [ + "memmap2", + "polars-arrow", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "rayon", + "recursive", +] + +[[package]] +name = "polars-ops" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b59c80a019ef0e6f09b4416d2647076a52839305c9eb11919e8298ec667f853" +dependencies = [ + "argminmax", + "base64", + "bytemuck", + "chrono", + "chrono-tz", + "either", + "hashbrown 0.16.0", + "hex", + "indexmap", + "libm", + "memchr", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-schema", + "polars-utils", + "rayon", + "regex", + "regex-syntax", + "strum_macros", + "unicode-normalization", + "unicode-reverse", + "version_check", +] + +[[package]] +name = "polars-parquet" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93c2439d127c59e6bfc9d698419bdb45210068a6f501d44e6096429ad72c2eaa" +dependencies = [ + "async-stream", + "base64", + "bytemuck", + "ethnum", + "futures", + "hashbrown 0.16.0", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-error", + "polars-parquet-format", + "polars-utils", + "serde", + "simdutf8", + "streaming-decompression", +] + +[[package]] +name = "polars-parquet-format" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c025243dcfe8dbc57e94d9f82eb3bef10b565ab180d5b99bed87fd8aea319ce1" +dependencies = [ + "async-trait", + "futures", +] + +[[package]] +name = "polars-plan" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b4619f5c7e9b91f18611c9ed82ebeee4b10052160825c1316ecf4dbd4d97e6" +dependencies = [ + "bitflags", + "bytemuck", + "bytes", + "chrono", + "chrono-tz", + "either", + "hashbrown 0.16.0", + "memmap2", + "num-traits", + "percent-encoding", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-io", + "polars-ops", + "polars-time", + "polars-utils", + "rayon", + "recursive", + "regex", + "sha2", + "slotmap", + "strum_macros", + "version_check", +] + +[[package]] +name = "polars-row" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18d232f25b83032e280a279a1f40beb8a6f8fc43907b13dc07b1c56f3b11eea" +dependencies = [ + "bitflags", + "bytemuck", + "polars-arrow", + "polars-compute", + "polars-dtype", + "polars-error", + "polars-utils", +] + +[[package]] +name = "polars-schema" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73e21d429ae1c23f442b0220ccfe773a9734a44e997b5062a741842909d9441" +dependencies = [ + "indexmap", + "polars-error", + "polars-utils", + "serde", + "version_check", +] + +[[package]] +name = "polars-sql" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e67ac1cbb0c972a57af3be12f19aa9803898863fe95c33cdd39df05f5738a75" +dependencies = [ + "bitflags", + "hex", + "polars-core", + "polars-error", + "polars-lazy", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "rand", + "regex", + "serde", + "sqlparser", +] + +[[package]] +name = "polars-stream" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ff19612074640a9d65e5928b7223db76ffee63e55b276f1e466d06719eb7362" +dependencies = [ + "async-channel", + "async-trait", + "atomic-waker", + "bitflags", + "chrono-tz", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "futures", + "memmap2", + "parking_lot", + "percent-encoding", + "pin-project-lite", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-ops", + "polars-parquet", + "polars-plan", + "polars-time", + "polars-utils", + "rand", + "rayon", + "recursive", + "slotmap", + "tokio", + "version_check", +] + +[[package]] +name = "polars-time" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddce7a9f81d5f47d981bcee4a8db004f9596bb51f0f4d9d93667a1a00d88166c" +dependencies = [ + "atoi_simd", + "bytemuck", + "chrono", + "chrono-tz", + "now", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-ops", + "polars-utils", + "rayon", + "regex", + "strum_macros", +] + +[[package]] +name = "polars-utils" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667c1bc2d2313f934d711f6e3b58d8d9f80351d14ea60af936a26b7dfb06e309" +dependencies = [ + "bincode", + "bytemuck", + "bytes", + "compact_str", + "either", + "flate2", + "foldhash 0.2.0", + "hashbrown 0.16.0", + "indexmap", + "libc", + "memmap2", + "num-traits", + "polars-error", + "rand", + "raw-cpuid", + "rayon", + "regex", + "rmp-serde", + "serde", + "serde_json", + "serde_stacker", + "slotmap", + "stacker", + "uuid", + "version_check", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppmd-rust" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c834641d8ad1b348c9ee86dec3b9840d805acd5f24daa5f90c788951a52ff59b" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "psm" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" +dependencies = [ + "cc", +] + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + +[[package]] +name = "quick-xml" +version = "0.38.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.0", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.0", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "quoted_printable" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "recursive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" +dependencies = [ + "recursive-proc-macro-impl", + "stacker", +] + +[[package]] +name = "recursive-proc-macro-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +dependencies = [ + "base64", + "bytes", + "cookie", + "cookie_store", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.3.0", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_stacker" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4936375d50c4be7eff22293a9344f8e46f323ed2b3c243e52f89138d9bb0f4a" +dependencies = [ + "serde", + "serde_core", + "stacker", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "sqlparser" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a528114c392209b3264855ad491fcce534b94a38771b0a0b97a79379275ce8" +dependencies = [ + "log", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stacker" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "streaming-decompression" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6cc3b19bfb128a8ad11026086e31d3ce9ad23f8ea37354b31383a187c44cf3" +dependencies = [ + "fallible-streaming-iterator", +] + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2 0.5.10", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-reverse" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6f4888ebc23094adfb574fdca9fdc891826287a6397d2cd28802ffd6f20c76" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.1", + "windows-result", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings 0.3.1", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.1", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link 0.1.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.1", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.3.3", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" + +[[package]] +name = "zopfli" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3997496 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "piperun-bot" +version = "0.1.0" +edition = "2024" + +[[bin]] +name = "groupped-repport-weekly" +path = "src/groupped_repport_weekly.rs" + +[[bin]] +name = "groupped-repport-monthly" +path = "src/groupped_repport_monthly.rs" + +[[bin]] +name = "piperun-bot" +path = "src/main.rs" + +[dependencies] +http = {version = "1.3.1"} +dotenv = {version = "0.15.0"} +serde_json = "1.0.145" +reqwest = { version = "0.12.23", features = ["json", "cookies", "blocking"] } +chrono = { version = "0.4.42" } +itertools = {version = "0.14.0"} +ipaddress = {version = "0.1.3"} +zip = { version = "6.0.0"} +walkdir = { version = "2.5.0"} +lettre = {version = "0.11.19", features = ["builder"]} +anyhow = { version = "1.0.100"} +polars = { version = "0.52.0"} +serde = { version = "1.0.228" } +csv = {version = "1.4.0"} +regex = { version = "1.12.2" } \ No newline at end of file diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..5756a2a --- /dev/null +++ b/Containerfile @@ -0,0 +1,19 @@ +FROM docker.io/rust:1.90 AS build +COPY . /app +WORKDIR /app +RUN git checkout main +RUN cargo build --release + +# FROM docker.io/alpine:3.22.1 AS production +FROM docker.io/debian:sid AS production +COPY --from=build /app/target/release/piperun-bot /app/ +COPY crontab /app/ + +WORKDIR /app +RUN ln -sf /bin/bash /bin/sh +RUN mkdir log +RUN chmod +x piperun-bot +RUN apt update && apt install cron -y +RUN /usr/bin/crontab crontab + +CMD ["cron", "-f"] \ No newline at end of file diff --git a/FILTER.txt b/FILTER.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/FILTER.txt @@ -0,0 +1 @@ + diff --git a/PROMPT.txt b/PROMPT.txt new file mode 100644 index 0000000..1d0e2d2 --- /dev/null +++ b/PROMPT.txt @@ -0,0 +1,43 @@ +SEGUINDO OS CRITÉRIOS QUE VÃO ESTAR ABAIXO, AVALIE ATENDIMENTOS PRESTADOS VIA CHAT PELO SETOR FINANCEIRO. + +01 (APRESENTAÇÃO) - O AGENTE DEVE SE APRESENTAR DURANTE O ATENDIMENTO. + +02 (CONFIRMAÇÃO DE E-MAIL) - O AGENTE DEVE SOLICITAR OU CONFIRMAR O E-MAIL DO CLIENTE DURANTE A CONVERSA. + +03 (CONFIRMAÇÃO DE TELEFONE) - O AGENTE DEVE SOLICITAR OU CONFIRMAR O TELEFONE DO TITULAR DURANTE A CONVERSA. + +04 (PROTOCOLO) - O AGENTE DEVE INFORMAR O PROTOCOLO DE ATENDIMENTO DURANTE A CONVERSA. + +05 (USO DO PORTUGUÊS) - O AGENTE DEVE UTILIZAR CORRETAMENTE O PORTUGUÊS (PONTUAÇÃO E ACENTUAÇÃO). +NÃO ESTÁ ERRADO SE O AGENTE UTILIZAR 'TU', 'TEU', 'TUA'. SOMOS UMA EMPRESA REGIONAL. + +06 (PACIÊNCIA E EDUCAÇÃO) - O AGENTE DEVE SER PACIENTE E EDUCADO, UTILIZANDO O USO DE AGRADECIMENTOS, SAUDAÇÕES, PEDIDOS DE DESCULPAS E LINGUAGEM RESPEITOSA. +COMO POR EXEMPLO "COMO POSSO TE AJUDAR?", "PERFEITO", "POR GENTILEZA", "OBRIGADO PELAS INFORMAÇÕES", "VOU VERIFICAR, AGUARDE UM MOMENTO POR GENTILEZA", "DESCULPE, MAS NÃO TE COMPREENDI", "PODERIA EXPLICAR DE NOVO?". + +07 (DISPONIBILIDADE) - O AGENTE DEVE SE COLOCAR À DISPOSIÇÃO DO CLIENTE, DEIXANDO CLARO QUE A EMPRESA ESTÁ SEMPRE DISPONÍVEL PARA AJUDAR. +COMO POR EXEMPLO "CASO TENHA ALGUMA DÚVIDA, NOS CONTATE. A EQUIPE DA NOVANET ESTÁ SEMPRE AQUI PARA TE AJUDAR." + +08 (ESCLARECIMENTO) - O AGENTE DEVE MANTER O CLIENTE INFORMADO DURANTE TODO O ATENDIMENTO, DEIXANDO CLARO O QUE ESTÁ SENDO FEITO, QUAL É A SITUAÇÃO ATUAL E O QUE ACONTECERÁ EM SEGUIDA. +ELE DEVE TRANSMITIR SEGURANÇA, MOSTRANDO QUE ESTÁ ACOMPANHANDO O CASO E EXPLICANDO AS ETAPAS DO PROCESSO. + +----------------------------------- + +As mensagens do chat estão estruturadas no formato JSON com os campos: +- **message**: conteúdo da mensagem +- **sent_at**: horário de envio +- **type**: tipo da mensagem ('IN', 'OUT' ou 'SYSTEM'). As mensagens do agente são sempre do tipo 'OUT'. +- **user_name**: nome do usuário que enviou a mensagem. Não considere respostas do PipeBot como do agente + +O fluxo de mensagens inicia-se com o cliente interagindo com o BOT, e então a mensagem é transferida para o atendente. + +Em cada categoria, atribua 0 quando o agente não tiver atendido o critétio e 1 caso tenha atendido. + +Devem ser avaliados somente os agentes que o nome de usuario inicie com "FIN -" . + +A sua resposta deve ser apenas uma tabela CSV e nada mais, utilizando como separador o caracter ';' com as colunas: CATEGORIA;PONTOS;MOTIVO;EVIDENCIA1;EVIDENCIA2; on> + +A evidencia1 deve ser diferente da evidencia2 mas ambas devem justificar a pontuação daquela categoria. + +Na saída CSV, na coluna categoria, utilize o nome correspondente ao invés do número + +A seguir estão as mensagens do atendimento, em JSON, avalie e retorne apenas um CSV. diff --git a/PROMPT_DATA_SANITIZATION.txt b/PROMPT_DATA_SANITIZATION.txt new file mode 100644 index 0000000..03515f4 --- /dev/null +++ b/PROMPT_DATA_SANITIZATION.txt @@ -0,0 +1,111 @@ +Abaixo está a avaliação de um atendimento que foi realizado. Eu preciso que a formatação fique consistente e padronizada. +Padronize o arquivo CSV da seguinte forma, deixando apenas as colunas listadas. +Título: CATEGORIA;PONTOS +A sua resposta deve ser apenas o CSV com a formatação corrigida, nada mais deve ser incluído na sua resposta, nem mesmo notas sobre a resposta. +Se não for possível padronizar o arquivo de entrada de acordo com as instruções fornecidas a resposta deve ser o CSV com o campo de pontuação vazio. +As categorias são: APRESENTAÇÃO, CONFIRMAÇÃO DE E-MAIL, CONFIRMAÇÃO DE TELEFONE, PROTOCOLO, USO DO PORTUGUÊS, PACIÊNCIA E EDUCAÇÃO, DISPONIBILIDADE, CONHECIMENTO TÉCNICO, DIDATISMO +A coluna pontos deve ter apenas os valores 0, 1 ou vazio, se no arquivo de entrada não houver a avaliação da categoria, a coluna de pontos deve ser vazia. + +Aqui estão alguns exemplos de formatação de como deve ser a sua resposta: +Exemplo 01: +Dado o seguinte arquivo de entrada: +APRESENTAÇÃO;1;O agente se apresentou ao cliente.;Boa noite, me chamo Ander.;Certo, um bom final de semana! 😊 +CONFIRMAÇÃO DE E-MAIL;1;O agente pediu confirmação do e-mail.;Para manter o cadastro atualizado, poderia me confirmar se o e-mail continua sendo janainads.sls@gmail.com e se o telefone do titular do cadastro permanece (53) 98446-2208?;Obrigado pela confirmação. +CONFIRMAÇÃO DE TELEFONE;1;O agente pediu confirmação do telefone.;Para manter o cadastro atualizado, poderia me confirmar se o e-mail continua sendo janainads.sls@gmail.com e se o telefone do titular do cadastro permanece (53) 98446-2208?;Obrigado pela confirmação. +PROTOCOLO;1;O agente informou o protocolo.;Aqui está o protocolo do teu atendimento: 2510.3624;Boa noite, me chamo Ander. +USO DO PORTUGUÊS;1;O agente utilizou português correto.;Aqui está o protocolo do teu atendimento: 2510.3624;Para manter o cadastro atualizado, poderia me confirmar se o e-mail continua sendo janainads.sls@gmail.com e se o telefone do titular do cadastro permanece (53) 98446-2208? +PACIÊNCIA E EDUCAÇÃO;1;O agente foi paciente e educado.;Obrigado pela confirmação.;Certo, um bom final de semana! 😊 +DISPONIBILIDADE;1;O agente demonstrou disponibilidade.;Caso tenha alguma dúvida, nos contate. A equipe da NovaNet está sempre aqui para te ajudar.😊;Certo, um bom final de semana! 😊 +CONHECIMENTO TÉCNICO;1;O agente identificou que não havia problemas nos equipamentos.;Verifiquei aqui os equipamentos e não identifiquei nenhum problema com eles.;A potência recebida pelo equipamento está normal e as configurações do roteador estão todas corretas. +DIDATISMO;1;O agente foi didático ao orientar o cliente.;Certo, tu poderia se conectar na rede do primeiro roteador e verificar se ocorre algum problema?;Entendi, pode ser algum problema neste segundo roteador, tu pode estar reiniciando ele na tomada para caso seja algum travamento. +100% + +A resposta sua deve ser: +```csv +CATEGORIA;PONTOS +APRESENTAÇÃO;1 +CONFIRMAÇÃO DE E-MAIL;1 +CONFIRMAÇÃO DE TELEFONE;1 +PROTOCOLO;1 +USO DO PORTUGUÊS;1 +PACIÊNCIA E EDUCAÇÃO;1 +DISPONIBILIDADE;1 +CONHECIMENTO TÉCNICO;1 +DIDATISMO;1 +``` + +Exemplo 02: +Dado o seguinte arquivo de entrada: +01,1,Apresentação,"Boa tarde, me chamo Ander. (12:10:05)" +02,1,Confirmou e‑mail,"Para manter o cadastro atualizado, poderia me confirmar se o e‑mail continua sendo mmaicomvoss@gmail.com? (13:01:40)" +03,1,Confirmou telefone,"para manter o cadastro atualizado, poderia me confirmar se o telefone continua (53) 98414‑3027? (13:01:40)" +04,1,Informa protocolo,"Aqui está o protocolo do teu atendimento: 2510.2749 (12:10:06)" +05,1,Uso correto do português,"Todas as mensagens foram escritas em português formal e correto, inclusive com 'tu' permitido. (12:10:05‑13:03:08)" +06,1,Uso de linguagem paciente e educada,"Aguarde meu retorno, por gentileza; me informe, por gentileza; obrigado pela confirmação. (12:10:13‑13:03:07)" +07,1,Disponibilidade expressa,"Caso tenha alguma dúvida, nos contate. A equipe da NovaNet está sempre aqui para te ajudar. (13:03:08)" +08,1,Conhecimento técnico,"Identificou mau contato no cabo, instruiu reconexões, avaliou ordem de serviço, cotou peças. (12:25:44‑12:57:10)" +09,1,Didático,"Desconecte o cabo LAN/WAN passo a passo e me informe quando terminar. (12:25:56‑12:26:06)" +10,1,Eclaração diagnóstica,"Fez diagnóstico de mau contato no cabo e ofereceu ordem de serviço sem custo. (12:55:17‑13:01:15)" +11,2,Tempo de espera excedido, 2 ocorrências,"Intervalos superiores a 5 min: 12:55:17‑13:01:15 (5 min 58 s) e 12:27:51‑12:54:11 (26 min 20 s)" + +A resposta sua deve ser: +```csv +CATEGORIA;PONTOS +APRESENTAÇÃO;1 +CONFIRMAÇÃO DE E-MAIL;1 +CONFIRMAÇÃO DE TELEFONE;1 +PROTOCOLO;1 +USO DO PORTUGUÊS;1 +PACIÊNCIA E EDUCAÇÃO;1 +DISPONIBILIDADE;1 +CONHECIMENTO TÉCNICO;1 +DIDATISMO;1 +``` + +Exemplo 03: +Dado o seguinte arquivo de entrada: +Identificação e abertura do atendimento,1, +Confirmação de dados do cliente,1, +Verificação do histórico e do plano do cliente,1, +Análise e diagnóstico da falha na conectividade,1, +Verificação e teste de equipamentos,1, +Sugestão de solução de conectividade,1, +Escala de serviço,1, +Encerramento de atendimento,1, +Follow-up,1, +Comunicação com o cliente,1, +Tempo de Resposta,1 + +A sua resposta deve ser vazia neste caso, pois a entrada não fornece a pontuação adequada para os critérios. +Ou seja o retorno deve ser o seguinte +```csv +CATEGORIA;PONTOS +APRESENTAÇÃO; +CONFIRMAÇÃO DE E-MAIL; +CONFIRMAÇÃO DE TELEFONE; +PROTOCOLO; +USO DO PORTUGUÊS; +PACIÊNCIA E EDUCAÇÃO; +DISPONIBILIDADE; +CONHECIMENTO TÉCNICO; +DIDATISMO; +``` + + +Aqui um exemplo de formatação de como não deve ser sua resposta +Erro 01: Não utilizar o formato estritamente como fornecido nas instruções e copiar da entrada que está sendo avaliada +```csv +CATEGORIA;PONTOS +APRESENTAÇÃO;1 +Confirmação de e-mail;1 +CONFIRMAÇÃO DE TELEFONE;1 +PROTOCOLO;1 +USO DO PORTUGUÊS;1 +PACIÊNCIA E EDUCAÇÃO;1 +DISPONIBILIDADE;1 +CONHECIMENTO TÉCNICO;1 +DIDATISMO;1 +``` + +Abaixo está a avaliação que deve ser processada +-------------------------------- diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..c6510ad --- /dev/null +++ b/compose.yaml @@ -0,0 +1,24 @@ +version: "3.9" + +networks: + piperun_bot_default: + external: true + ipv6_enabled: true + +services: + piperun-bot: + name: piperun-bot + restart: always + build: + context: . + dockerfile: Containerfile + volumes: + - ./crontab:/app/crontab:ro + - ./PROMPT.txt:/app/PROMPT.txt + - ./FILTER.txt:/app/FILTER.txt + - ./evaluations:/app/evaluations + - ./.env:/app/.env + - ./log/container:/app/log + - /etc/localtime:/etc/localtime:ro # sync time between machine and container + networks: + - piperun_bot_default diff --git a/crontab b/crontab new file mode 100644 index 0000000..477592a --- /dev/null +++ b/crontab @@ -0,0 +1,13 @@ +# +# Output of the crontab jobs (including errors) is sent through +# email to the user the crontab file belongs to (unless redirected). +# +# For example, you can run a backup of all your user accounts +# at 5 a.m every week with: +# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/ +# +# For more information see the manual pages of crontab(5) and cron(8) +# +# m h dom mon dow command +#0 1 * * * piperun-bot # Execute the piperun-bot everyday at 01h00min +25 0 * * * cd /app; ./piperun-bot >/app/log/stdout.log 2>/app/log/stderr.log # Execute the piperun-bot every minute diff --git a/src/.env.example b/src/.env.example new file mode 100644 index 0000000..1e36132 --- /dev/null +++ b/src/.env.example @@ -0,0 +1,7 @@ +OLLAMA_URL=localhost +OLLAMA_PORT=11432 +PIPERUN_API_URL=novanet.cxm.pipe.run +PIPERUN_CLIENT_ID=0 +PIPERUN_CLIENT_SECRET= +PIPERUN_BOT_USERNAME= +PIPERUN_BOT_PASSWORD= \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..cd3edca --- /dev/null +++ b/src/config.rs @@ -0,0 +1,7 @@ +const OLLAMA_URL: str = "localhost"; +const OLLAMA_PORT: str = 11434; +const PIPERUN_API_URL: str = "novanet.cxm.pipe.run"; +const PIPERUN_CLIENT_ID: i32 = 0; +const PIPERUN_CLIENT_SECRET: str = ""; +const PIPERUN_BOT_USERNAME: str = ""; +const PIPERUN_BOT_PASSWORD: str = ""; diff --git a/src/fin_main.rs b/src/fin_main.rs new file mode 100644 index 0000000..e4ed571 --- /dev/null +++ b/src/fin_main.rs @@ -0,0 +1,673 @@ +use std::{any::Any, env, fmt::format, iter, time::Duration}; + +use chrono::{self, Timelike}; +use dotenv; +use ipaddress; +use itertools::{self, Itertools}; +use lettre::{self, message}; +use reqwest; +use serde_json::{self, json}; + +use std::io::prelude::*; + +pub mod send_mail_util; +pub mod zip_directory_util; + +fn main() -> anyhow::Result<()> { + match dotenv::dotenv().ok() { + Some(_) => println!("Environment variables loaded from .env file"), + None => eprintln!("Failed to load .env file, using defaults"), + } + + // Read environment variables + let OLLAMA_URL = env::var("OLLAMA_URL").unwrap_or("localhost".to_string()); + let OLLAMA_PORT = env::var("OLLAMA_PORT") + .unwrap_or("11432".to_string()) + .parse::() + .unwrap_or(11432); + let PIPERUN_API_URL = env::var("PIPERUN_API_URL").expect("PIPERUN_API_URL has not been set!"); + let PIPERUN_CLIENT_ID = env::var("PIPERUN_CLIENT_ID") + .expect("PIPERUN_CLIENT_ID has not been set!") + .parse::() + .unwrap_or(0); + let PIPERUN_CLIENT_SECRET = + env::var("PIPERUN_CLIENT_SECRET").expect("PIPERUN_CLIENT_SECRET has not been set!"); + let PIPERUN_BOT_USERNAME = + env::var("PIPERUN_BOT_USERNAME").expect("PIPERUN_BOT_USERNAME has not been set!"); + let PIPERUN_BOT_PASSWORD = + env::var("PIPERUN_BOT_PASSWORD").expect("PIPERUN_BOT_PASSWORD has not been set!"); + let OLLAMA_AI_MODEL = env::var("OLLAMA_AI_MODEL").expect("OLLAMA_AI_MODEL has not been set!"); + let MINIMUM_NUMBER_OF_MESSAGES_TO_EVALUATE = env::var("MINIMUM_NUMBER_OF_MESSAGES_TO_EVALUATE") + .expect("MINIMUM_NUMBER_OF_MESSAGES_TO_EVALUATE has not been set!") + .parse::() + .unwrap_or(10); + let MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE = + env::var("MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE") + .expect("MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE has not been set!") + .parse::() + .unwrap_or(12); + let BOT_EMAIL = env::var("BOT_EMAIL").expect("BOT_EMAIL has not been set!"); + let BOT_EMAIL_PASSWORD = + env::var("BOT_EMAIL_PASSWORD").expect("BOT_EMAIL_PASSWORD has not been set!"); + + // Print the configuration + println!("OLLAMA_URL: {}", OLLAMA_URL); + println!("OLLAMA_PORT: {}", OLLAMA_PORT); + println!("OLLAMA_AI_MODEL: {}", OLLAMA_AI_MODEL); + println!("PIPERUN_API_URL: {}", PIPERUN_API_URL); + println!("PIPERUN_CLIENT_ID: {}", PIPERUN_CLIENT_ID); + println!("PIPERUN_CLIENT_SECRET: {}", PIPERUN_CLIENT_SECRET); + println!("PIPERUN_BOT_USERNAME: {}", PIPERUN_BOT_USERNAME); + println!("PIPERUN_BOT_PASSWORD: {}", PIPERUN_BOT_PASSWORD); + + let ip_address = ipaddress::IPAddress::parse(OLLAMA_URL.to_string()); + let OLLAMA_SANITIZED_IP = match ip_address { + Ok(ip) => { + if ip.is_ipv4() { + OLLAMA_URL.clone() + } else { + format!("[{}]", OLLAMA_URL.clone()) + } + } + Err(e) => OLLAMA_URL.clone(), + }; + + // Send the authentication request + let client = reqwest::blocking::Client::new(); + + let auth_request = client + .post(format!("https://{}/oauth/token", PIPERUN_API_URL)) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .body( + serde_json::json!({ + "grant_type": "password", + "client_id": PIPERUN_CLIENT_ID, + "client_secret": PIPERUN_CLIENT_SECRET, + "username": PIPERUN_BOT_USERNAME, + "password": PIPERUN_BOT_PASSWORD, + }) + .to_string(), + ); + + println!("Sending authentication request to Piperun API..."); + println!("{:?}", auth_request); + + let response = auth_request.send(); + let access_token = match response { + Ok(resp) => { + if resp.status().is_success() { + let json: serde_json::Value = resp.json()?; + // println!("Authentication successful: {:?}", json); + + // Extract the access token + if let Some(access_token) = json.get("access_token") { + println!("Access Token: {}", access_token); + access_token + .as_str() + .expect("Failed to get token") + .to_string() + } else { + eprintln!("Access token not found in response"); + panic!("Failed to retrieve access token from Piperun API"); + } + } else { + eprintln!("Authentication failed: {}", resp.status()); + let json: serde_json::Value = resp.json()?; + eprintln!("Response body: {:?}", json); + + panic!("Failed to authenticate with Piperun API"); + } + } + Err(e) => { + eprintln!("Error sending authentication request: {}", e); + panic!("Failed to send authentication request to Piperun API"); + } + }; + + // Get the current day in the format YYYY-MM-DD + let current_date = chrono::Local::now(); + let formatted_date = current_date.format("%Y-%m-%d").to_string(); + + println!("Current date: {}", formatted_date); + + // Get the day before the current date + let day_before = current_date + .checked_sub_signed(chrono::Duration::days(1)) + .expect("Failed to get the day before"); + let formatted_day_before = day_before.format("%Y-%m-%d").to_string(); + + println!("Day before: {}", formatted_day_before); + + let day_before_at_midnight = day_before + .with_hour(0) + .unwrap() + .with_minute(0) + .unwrap() + .with_second(0) + .unwrap(); + let formatted_day_before_at_midnight = + day_before_at_midnight.format("%Y-%m-%d %H:%M").to_string(); + + let day_before_at_23_59_59 = day_before + .with_hour(23) + .unwrap() + .with_minute(59) + .unwrap() + .with_second(59) + .unwrap(); + let formatted_day_before_at_23_59_59 = + day_before_at_23_59_59.format("%Y-%m-%d %H:%M").to_string(); + + println!( + "Day before at midnight: {}, Day before at 23:59:59: {}", + formatted_day_before_at_midnight, formatted_day_before_at_23_59_59 + ); + + let formatted_day_before = day_before_at_midnight.format("%Y-%m-%d").to_string(); + + // Create a folder named with the day_before + if !std::fs::exists(format!("./evaluations/{formatted_day_before}")).unwrap() { + std::fs::create_dir(format!("./evaluations/{formatted_day_before}")) + .expect("Failed to create directory") + } + + // Create the response time folder + if !std::fs::exists(format!( + "./evaluations/{formatted_day_before}/response_time.csv" + )) + .unwrap() + { + let mut response_time_file = std::fs::File::create_new(format!( + "./evaluations/{formatted_day_before}/response_time.csv" + )) + .expect("Failed to response_time.csv"); + } + + // Read system prompt + let prompt = std::fs::read_to_string("PROMPT.txt").unwrap(); + let filter_file_contents = std::fs::read_to_string("FILTER.txt").unwrap_or(String::new()); + let filter_keywords = filter_file_contents + .split("\n") + .filter(|keyword| !keyword.is_empty()) + .collect::>(); + + let talks_array = get_piperun_chats_on_date( + &PIPERUN_API_URL, + &client, + &access_token, + formatted_day_before_at_midnight, + formatted_day_before_at_23_59_59, + ); + + println!("Number of consolidated talks: {}", talks_array.len()); + + let talk_ids = talks_array + .iter() + .cloned() + .map(|value| { + serde_json::from_value::(value).expect("Failed to parse the JSON") + ["id"] + .clone() + .to_string() + }) + .collect::>(); + + println!("IDS {:?}", talk_ids); + + // Gather messages and apply filtering + let filtered_chats = talk_ids + .iter() + .cloned() + .map(|talk_id| { + let talk_id_get_request = client + .get(format!("https://{}/api/talk_histories", PIPERUN_API_URL)) + .bearer_auth(&access_token) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .query(&[ + ("talk_id", talk_id), + ("type", "a".to_string()), + ("only_view", "1".to_string()), + ]); + + let talk_id_get_result = talk_id_get_request.send(); + + return talk_id_get_result; + }) + .filter_map_ok(|result| { + let json = result + .json::() + .expect("Failed to deserialize response to JSON") + .to_owned(); + let talk_histories = &json["talk_histories"]; + let data = &talk_histories["data"]; + + // Filter chats that have very few messages + let talk_lenght = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .len(); + if talk_lenght < MINIMUM_NUMBER_OF_MESSAGES_TO_EVALUATE { + return None; + } + + // Filter chats that have less that specified ammount of talks with support agent form the last queue transfer + let found = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .into_iter() + .enumerate() + .find(|(pos, message_object)| { + let message = message_object["message"] + .as_str() + .expect("Failed to decode message as string"); + let found = message.find( + "Atendimento transferido para a fila [NovaNet -> Atendimento -> Financeiro NVL2]", + ); + found.is_some() + }); + + match found { + None => { + return None; + } + Some(pos) => { + let pos_found = pos.0; + if pos_found < MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE { + return None; + } + } + }; + + // Filter Bot finished chats + if json["agent"]["user"]["name"] + .as_str() + .unwrap_or("unknown_user") + == "PipeBot" + { + return None; + } + + // Apply keyword based filtering + let filter_keywords_found = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .into_iter() + .any(|message_object| { + let message = message_object["message"] + .as_str() + .expect("Failed to decode message as string"); + let found1 = filter_keywords.iter().any(|keyword| { + message + .to_uppercase() + .find(&keyword.to_uppercase()) + .is_some() + }); + let found2 = message_object["is_template"] + .as_bool() + .unwrap_or(true); + let found = found1 || found2; + found + }); + + if filter_keywords_found { + return None; + } + + return Some(json); + }); + + // Calculate the response time in seconds + let response_time = filtered_chats + .clone() + .map(|messages| { + let json = messages.unwrap(); + let talk_histories = &json["talk_histories"]; + + // dbg!(&talk_histories); + + // talk_histories.as_array().unwrap().into_iter().enumerate().for_each(|(pos, message_obj)|{println!("{}: {}", pos, message_obj["message"])}); + + // find the bot transfer message + let bot_transfer_message = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .into_iter() + .enumerate() + .filter(|(pos, message_object)| { + let user_name = message_object["user"]["name"] + .as_str() + .expect("Failed to decode message as string"); + user_name == "PipeBot".to_string() + }) + .find(|(pos, message_object)| { + let message = message_object["message"] + .as_str() + .expect("Failed to decode message as string"); + //let found = message.find("Atendimento transferido para a fila [NovaNet -> Atendimento -> Financeiro NVL2]"); + let found = message.find("Atendimento entregue da fila de espera para o agente [FIN - "); + found.is_some() + }); + + // Find first agent message sent after the last bot message + let (pos, transfer_message) = + bot_transfer_message.expect("Failed to get the transfer bot message position"); + + let msg = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .into_iter() + .take(pos) + .rev() + .filter(|message| { + message["type"] == "out".to_string() + // && message["user"]["name"] != "PipeBot".to_string() + && message["user"]["name"].as_str().map_or(false, |name| name.starts_with("FIN -")) + }) + .take(1) + .collect_vec(); + + //if msg[0] { + // return None; + //} + let agent_first_message = msg[0]; + + // Calculate time difference between bot message and agent message + let date_user_message_sent = agent_first_message["sent_at"].as_str().unwrap(); + + let format = "%Y-%m-%d %H:%M:%S"; + + let date_user_message_sent_parsed = + match chrono::NaiveDateTime::parse_from_str(date_user_message_sent, format) { + Ok(dt) => dt, + Err(e) => { + println!("Error parsing DateTime: {}", e); + panic!("Failed parsing date") + } + }; + + let date_transfer_message_sent_parsed = match chrono::NaiveDateTime::parse_from_str( + transfer_message["sent_at"].as_str().unwrap(), + format, + ) { + Ok(dt) => dt, + Err(e) => { + println!("Error parsing DateTime: {}", e); + panic!("Failed parsing date") + } + }; + + let response_time = (date_user_message_sent_parsed - date_transfer_message_sent_parsed) + .as_seconds_f32(); + let name = agent_first_message["user"]["name"] + .as_str() + .unwrap() + .to_owned(); + let id = json["tracking_number"].as_str().unwrap_or("").to_owned(); + let bot_transfer_date = date_transfer_message_sent_parsed.to_owned(); + let user_response_date = date_user_message_sent.to_owned(); + println!( + "response_time: {}s", + (date_user_message_sent_parsed - date_transfer_message_sent_parsed) + .as_seconds_f32() + ); + + format!( + "{};{};{};{};{}", + name, id, response_time, bot_transfer_date, user_response_date + ) + }) + .reduce(|acc, e| format!("{}\n{}", acc, e)) + .unwrap_or("".to_string()); + + // return Ok(()); + // Open file and write to it + let header = "NOME;ID_TALK;TEMPO DE RESPOSTA;TRANFERENCIA PELO BOT;PRIMEIRA RESPOSTA DO AGENTE"; + let mut response_time_file = std::fs::OpenOptions::new() + .write(true) + .open(format!( + "./evaluations/{formatted_day_before}/response_time.csv" + )) + .expect("Failed to open response time file for write"); + response_time_file + .write_all(format!("{header}\n{response_time}").as_bytes()) + .expect("Failed to write header to file"); + + filtered_chats.clone().skip(0).for_each(|result| { + let json = result.unwrap(); + let talk_histories = &json["talk_histories"]; + let data = &talk_histories["data"]; + + let talk = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .iter() + .rev() + .map(|message_object| { + let new_json_filtered = format!( + "{{ + message: {}, + sent_at: {}, + type: {}, + user_name: {} +}}", + message_object["message"], + message_object["sent_at"], + message_object["type"], + message_object["user"]["name"] + ); + // println!("{}", new_json_filtered); + new_json_filtered + }) + .reduce(|acc, e| format!("{acc}\n{e}")) + .expect("Error extracting talk"); + + println!("{prompt}\n {talk}"); + + let ollama_api_request = client + .post(format!( + "http://{OLLAMA_SANITIZED_IP}:{OLLAMA_PORT}/api/generate" + )) + .body( + serde_json::json!({ + "model": OLLAMA_AI_MODEL, + "prompt": format!("{prompt} \n{talk}"), + // "options": serde_json::json!({"temperature": 0.1}), + "stream": false, + }) + .to_string(), + ); + + let result = ollama_api_request.timeout(Duration::from_secs(3600)).send(); + + match result { + Ok(response) => { + println!("Response: {:?}", response); + let response_json = response + .json::() + .expect("Failed to deserialize response to JSON"); + println!("{}", response_json); + let ai_response = response_json["response"] + .as_str() + .expect("Failed to get AI response as string"); + println!("AI Response: {}", ai_response); + + let csv_response = ai_response.replace("```csv\n", "").replace("```", ""); + + // Save the CSV response to a file + + let user_name = &json["agent"]["user"]["name"] + .as_str() + .unwrap_or("unknown_user"); + let talk_id = &json["id"].as_u64().unwrap_or(0); + let tracking_number = &json["tracking_number"].as_str().unwrap_or(""); + std::fs::write( + format!( + "./evaluations/{}/{} - {} - {}.csv", + formatted_day_before, user_name, talk_id, tracking_number + ), + csv_response, + ) + .expect("Unable to write file"); + std::fs::write( + format!( + "./evaluations/{}/{} - {} - {} - prompt.txt", + formatted_day_before, user_name, talk_id, tracking_number + ), + format!("{prompt} \n{talk}"), + ) + .expect("Unable to write file"); + } + Err(error) => { + println!("Error {error}"); + } + }; + }); + + // Compress folder into zip + let source_dir_str = format!("./evaluations/{formatted_day_before}"); + let output_zip_file_str = format!("./evaluations/{formatted_day_before}.zip"); + let source_dir = std::path::Path::new(source_dir_str.as_str()); + let output_zip_file = std::path::Path::new(output_zip_file_str.as_str()); + zip_directory_util::zip_directory_util::zip_source_dir_to_dst_file(source_dir, output_zip_file); + + // Send folder to email + //let recipients = "Wilson da Conceição Oliveira , Isadora G. Moura de Moura "; + let recipients = "Wilson da Conceição Oliveira "; + println!("Trying to send email... Recipients {recipients}"); + + send_mail_util::send_mail_util::send_email( + &format!("Avaliacao atendimentos {formatted_day_before}"), + &BOT_EMAIL, + &BOT_EMAIL_PASSWORD, + recipients, + &output_zip_file_str, + ); + + return Ok(()); +} + +fn get_piperun_chats_on_date( + PIPERUN_API_URL: &String, + client: &reqwest::blocking::Client, + access_token: &String, + formatted_day_before_at_midnight: String, + formatted_day_before_at_23_59_59: String, +) -> Vec { + let start_of_talk_code: String = "talk_start".to_string(); + let support_queue_id: String = "16".to_string(); + + // API V2 + let report_type = "consolidated".to_string(); + let page = "1".to_string(); + let per_page = "15".to_string(); + + let talks_request = client + .get(format!("https://{}/api/v2/reports/talks", PIPERUN_API_URL)) + .bearer_auth(access_token) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .query(&[ + ("page", page.clone()), + ("perPage", per_page.clone()), + ("report_type", report_type.clone()), + ("start_date", formatted_day_before_at_midnight.clone()), + ("end_date", formatted_day_before_at_23_59_59.clone()), + ("date_range_type", start_of_talk_code.clone()), + ("queue_id[]", support_queue_id.clone()), + ]); + + println!("Sending request for consolidated talks... {talks_request:?}"); + let talks_response = talks_request.send(); + + let json_response = match talks_response { + Ok(resp) => { + if resp.status().is_success() { + let json: serde_json::Value = resp.json().unwrap(); + json + } else { + eprintln!("Failed to get consolidated talks: {}", resp.status()); + let json: serde_json::Value = resp.json().unwrap(); + eprintln!("Response body: {:?}", json); + panic!("Failed to retrieve consolidated talks from Piperun API"); + } + } + Err(e) => { + eprintln!("Error: {e}"); + panic!("Failed to send the request for talks to PipeRUN API"); + } + }; + + let mut aggregated_talks = json_response["data"] + .as_array() + .expect("Failed to parse messages as array") + .to_owned(); + + let current_page = json_response["current_page"] + .as_i64() + .expect("Failed to obtain current page number"); + let last_page = json_response["last_page"] + .as_i64() + .expect("Failed to obtain current page number"); + + if current_page == last_page { + return aggregated_talks; + } + + let mut all_other_messages = (current_page..last_page) + .into_iter() + .map(|page| { + let page_to_request = page + 1; + let talks_request = client + .get(format!("https://{}/api/v2/reports/talks", PIPERUN_API_URL)) + .bearer_auth(access_token) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .query(&[ + ("page", page_to_request.to_string()), + ("perPage", per_page.clone()), + ("report_type", report_type.clone()), + ("start_date", formatted_day_before_at_midnight.clone()), + ("end_date", formatted_day_before_at_23_59_59.clone()), + ("date_range_type", start_of_talk_code.clone()), + ("queue_id[]", support_queue_id.clone()), + ]); + + println!("Sending request for consolidated talks... {talks_request:?}"); + let talks_response = talks_request.send(); + + let json_response = match talks_response { + Ok(resp) => { + if resp.status().is_success() { + let json: serde_json::Value = resp.json().unwrap(); + json + } else { + eprintln!("Failed to get consolidated talks: {}", resp.status()); + let json: serde_json::Value = resp.json().unwrap(); + eprintln!("Response body: {:?}", json); + panic!("Failed to retrieve consolidated talks from Piperun API"); + } + } + Err(e) => { + eprintln!("Error: {e}"); + panic!("Failed to send the request for talks to PipeRUN API"); + } + }; + + let aggregated_talks = json_response["data"] + .as_array() + .expect("Failed to parse messages as array") + .to_owned(); + + return aggregated_talks; + }) + .reduce(|mut this, mut acc| { + acc.append(&mut this); + acc + }) + .expect("Failed to concatenate all vectors of messages"); + + aggregated_talks.append(&mut all_other_messages); + aggregated_talks +} diff --git a/src/groupped_repport_monthly.rs b/src/groupped_repport_monthly.rs new file mode 100644 index 0000000..3050443 --- /dev/null +++ b/src/groupped_repport_monthly.rs @@ -0,0 +1,506 @@ +use std::fmt::Debug; + +use chrono::{Datelike, NaiveDate}; +use itertools::Itertools; +use polars::prelude::*; +use reqwest; +use std::env; +use std::time::Duration; + +use csv; + +pub mod send_mail_util; +pub mod zip_directory_util; + +#[derive(Debug, serde::Deserialize)] +struct CsvHeader { + CATEGORIA: String, + PONTOS: Option, +} + +#[derive(Debug, serde::Deserialize)] +struct CsvEvaluation { + APRESENTAÇÃO: u8, + CONFIRMAÇÃO_DE_EMAIL: u8, + CONFIRMAÇÃO_DE_TELEFONE: u8, + PROTOCOLO: u8, + USO_DO_PORTUGUÊS: u8, + PACIÊNCIA_E_EDUCAÇÃO: u8, + DISPONIBILIDADE: u8, + CONHECIMENTO_TÉCNICO: u8, + DIDATISMO: u8, + ID_TALK: String, +} + +// --- ADICAO PARA AGRUPAR O response_time.csv --- +#[derive(Debug, serde::Deserialize)] +struct ResponseTimeRecord { + NOME: String, + ID_TALK: String, + #[serde(rename = "TEMPO DE RESPOSTA")] + TEMPO_DE_RESPOSTA: u32, + #[serde(rename = "TRANFERENCIA PELO BOT")] + TRANFERENCIA_PELO_BOT: String, + #[serde(rename = "PRIMEIRA RESPOSTA DO AGENTE")] + PRIMEIRA_RESPOSTA_DO_AGENTE: String, +} +// --- FIM DA ADIÇÃO --- + +fn main() { + match dotenv::dotenv().ok() { + Some(_) => println!("Environment variables loaded from .env file"), + None => eprintln!("Failed to load .env file, using defaults"), + } + + // Read environment variables + let OLLAMA_URL = env::var("OLLAMA_URL").unwrap_or("localhost".to_string()); + let OLLAMA_PORT = env::var("OLLAMA_PORT") + .unwrap_or("11432".to_string()) + .parse::() + .unwrap_or(11432); + let OLLAMA_AI_MODEL_DATA_SANITIZATION = env::var("OLLAMA_AI_MODEL_DATA_SANITIZATION") + .expect("Missing environment variable OLLAMA_AI_MODEL_DATA_SANITIZATION"); + let BOT_EMAIL = env::var("BOT_EMAIL").expect("BOT_EMAIL has not been set!"); + let BOT_EMAIL_PASSWORD = + env::var("BOT_EMAIL_PASSWORD").expect("BOT_EMAIL_PASSWORD has not been set!"); + + let ip_address = ipaddress::IPAddress::parse(OLLAMA_URL.to_string()); + let OLLAMA_SANITIZED_IP = match ip_address { + Ok(ip) => { + if ip.is_ipv4() { + OLLAMA_URL.clone() + } else { + format!("[{}]", OLLAMA_URL.clone()) + } + } + Err(e) => OLLAMA_URL.clone(), + }; + + // Get the current day in the format YYYY-MM-DD + let current_date = chrono::Local::now(); + let formatted_date = current_date.format("%Y-%m-%d").to_string(); + + let current_date = chrono::Local::now(); + // let first_day_of_current_month = NaiveDate::fro + + let current_day_of_last_month = current_date + .checked_sub_months(chrono::Months::new(1)) + .expect("Failed to subtract one month"); + + let first_day_of_last_month = NaiveDate::from_ymd_opt( + current_day_of_last_month.year(), + current_day_of_last_month.month(), + 1, + ) + .expect("Failed to obtain date"); + let last_day_of_last_month = NaiveDate::from_ymd_opt( + current_day_of_last_month.year(), + current_day_of_last_month.month(), + current_day_of_last_month.num_days_in_month() as u32, + ) + .expect("Failed to obtain date"); + + let previous_month_folder_names = std::fs::read_dir(std::path::Path::new("./evaluations")) + .expect("Failed to read directory ./evaluations") + .filter_map_ok(|entry| { + if entry.metadata().unwrap().is_dir() { + Some(entry.file_name()) + } else { + None + } + }) + .filter_map_ok(|entry_string_name| { + let regex_match_date = + regex::Regex::new(r"(\d{4}-\d{2}-\d{2})").expect("Failed to build regex"); + + let filename = entry_string_name.to_str().unwrap(); + let matches_find = regex_match_date.find(filename); + + match matches_find { + Some(found) => { + let date = chrono::NaiveDate::parse_from_str(found.as_str(), "%Y-%m-%d"); + return Some((date.unwrap(), entry_string_name)); + } + None => { + return None; + } + }; + }) + .filter_map_ok(|(folder_name_date, directory_string)| { + if folder_name_date.year() == first_day_of_last_month.year() + && folder_name_date.month() == first_day_of_last_month.month() + { + return Some(directory_string); + } + return None; + }) + .filter_map(|value| { + if value.is_ok() { + return Some(value.unwrap()); + } else { + return None; + } + }) + .sorted() + .collect_vec(); + + println!("{:?}", previous_month_folder_names); + + let prompt_data_sanitization = std::fs::read_to_string("./PROMPT_DATA_SANITIZATION.txt") + .expect("Failed to read PROMPT_DATA_SANITIZATION.txt"); + let client = reqwest::blocking::Client::new(); + + let groupped_values = previous_month_folder_names + .iter() + .map(|folder_name| { + let folder_base_path = std::path::Path::new("./evaluations"); + let folder_date_path = folder_base_path.join(folder_name); + std::fs::read_dir(folder_date_path) + }) + .filter_map_ok(|files_inside_folder_on_date| { + let groupped_by_user_on_day = files_inside_folder_on_date + .filter_ok(|entry| { + let entry_file_name_as_str = entry + .file_name() + .into_string() + .expect("Failed to get filename as a String"); + + entry_file_name_as_str.ends_with(".csv") + && !entry_file_name_as_str.contains("response_time.csv") + }) + .filter_map(|value| { + if value.is_ok() { + return Some(value.unwrap()); + } + None + }) + .map(|file_name_csv| { + println!("{:?}", file_name_csv.path()); + let file_contents = std::fs::read_to_string(file_name_csv.path()) + .expect("Failed to read CSV file"); + + let ollama_api_request = client + .post(format!( + "http://{OLLAMA_SANITIZED_IP}:{OLLAMA_PORT}/api/generate" + )) + .body( + serde_json::json!({ + "model": OLLAMA_AI_MODEL_DATA_SANITIZATION, + "prompt": format!("{prompt_data_sanitization} \n{file_contents}"), + "temperature": 0.0, // Get predictable and reproducible output + "stream": false, + }) + .to_string(), + ); + + let result = ollama_api_request.timeout(Duration::from_secs(3600)).send(); + + match result { + Ok(response) => { + println!("Response: {:?}", response); + let response_json = response + .json::() + .expect("Failed to deserialize response to JSON"); + let ai_response = response_json["response"] + .as_str() + .expect("Failed to get AI response as string"); + + let ai_response = ai_response.to_string(); + + let ai_response = if let Some(resp) = ai_response + .strip_prefix(" ") + .unwrap_or(&ai_response) + .strip_prefix("```csv\n") + { + resp.to_string() + } else { + ai_response + }; + let ai_response = if let Some(resp) = ai_response + .strip_suffix(" ") + .unwrap_or(&ai_response) + .strip_suffix("```") + { + resp.to_string() + } else { + ai_response + }; + + return Ok((ai_response, file_name_csv)); + } + Err(error) => { + println!("Error {error}"); + return Err(error); + } + }; + }) + .filter_map_ok(|(ai_repsonse, file_path_csv)| { + let mut reader = csv::ReaderBuilder::new() + .has_headers(true) + .delimiter(b';') + .from_reader(ai_repsonse.as_bytes()); + + let mut deserialized_iter = reader.deserialize::(); + + let mut columns = deserialized_iter + .filter_ok(|value| value.PONTOS.is_some()) + .map_ok(|value| { + let col = + Column::new(value.CATEGORIA.into(), [value.PONTOS.unwrap() as u32]); + col + }) + .filter_map(|value| { + if value.is_ok() { + return Some(value.unwrap()); + } + None + }) + .collect_vec(); + + if columns.len() != 9 { + return None; + } + + // Parse id talk from file_path + // filename example is: CC - Erraoander Quintana - 515578 - 20251020515578.csv + // id talk is the last information, so in the example is: 20251020515578 + let regex_filename = + regex::Regex::new(r"(CC - )((\w+\s*)+) - (\d+) - (\d+).csv").unwrap(); + + let filename = file_path_csv + .file_name() + .into_string() + .expect("Failed to convert file name as Rust &str"); + let found_regex_groups_in_filename = regex_filename + .captures(filename.as_str()) + .expect("Failed to do regex capture"); + + let user_name = found_regex_groups_in_filename + .get(2) + .expect("Failed to get the id from regex maches"); + let talk_id = found_regex_groups_in_filename + .get(5) + .expect("Failed to get the id from regex maches"); + + let excelence_percentual = columns + .iter() + .map(|col| col.as_materialized_series().u32().unwrap().sum().unwrap()) + .sum::() as f32 + / columns.iter().len() as f32 + * 100.0; + columns.push(Column::new( + "PERCENTUAL DE EXELENCIA".into(), + [format!("{excelence_percentual:.2}")], + )); + + columns.push(Column::new("ID_TALK".into(), [talk_id.clone().as_str()])); + + let df = polars::frame::DataFrame::new(columns) + .expect("Failed to concatenate into a dataframe"); + + // return a tuple with the dataframe and the user name, so it can be correctly merged after + return Some((user_name.as_str().to_owned(), df)); + }) + .filter_map(|res| { + if res.is_ok() { + return Some(res.unwrap()); + } + return None; + }) + .into_group_map() + .into_iter() + .map(|(name, eval_dataframe_vec)| { + let groupped_df = eval_dataframe_vec + .iter() + .cloned() + .reduce(|acc, e| acc.vstack(&e).unwrap()) + .expect("Failed to concatenate dataframes"); + (name, groupped_df) + }) + .into_group_map(); + + dbg!(&groupped_by_user_on_day); + return Some(groupped_by_user_on_day); + }) + .filter_map(|res| { + if res.is_ok() { + return Some(res.unwrap()); + } + return None; + }) + .reduce(|mut acc, mut e| { + e.iter_mut().for_each(|(key, val)| { + if acc.contains_key(key) { + acc.get_mut(key) + .expect("Failed to obtain key that should already be present") + .append(val); + } else { + acc.insert(key.to_owned(), val.to_owned()); + } + }); + acc + }) + .and_then(|groupped_hashmap_df| { + let result = groupped_hashmap_df + .iter() + .map(|(key, val)| { + let dfs = val + .iter() + .cloned() + .reduce(|acc, e| acc.vstack(&e).unwrap()) + .expect("Failed to concatenate dataframes"); + (key.clone(), dfs) + }) + .collect_vec(); + return Some(result); + }); + + // Setup groupped folder + if !std::fs::exists(format!("./groupped/")).unwrap() { + std::fs::create_dir(format!("./groupped")).expect("Failed to create directory") + } + + // Setup previous week folder + if !std::fs::exists(format!("./groupped/{first_day_of_last_month}")).unwrap() { + std::fs::create_dir(format!("./groupped/{first_day_of_last_month}")) + .expect("Failed to create directory") + } + + match groupped_values { + Some(mut val) => { + val.iter_mut().for_each(|(agent, groupped_evaluations)| { + let mut save_file_csv = std::fs::File::create(format!( + "./groupped/{first_day_of_last_month}/{agent}.csv" + )) + .expect("Could not create csv file for saving"); + CsvWriter::new(&mut save_file_csv) + .include_header(true) + .with_separator(b';') + .finish(groupped_evaluations) + .expect("Failed to save Groupped DataFrame to CSV File"); + }); + } + None => {} + } + +// --- ADICAO DO PROCESSAMENTO MENSAL DO response_time.csv --- + let response_times_data = previous_month_folder_names + .iter() + .map(|folder_name| { + let folder_base_path = std::path::Path::new("./evaluations"); + let folder_date_path = folder_base_path.join(folder_name); + std::fs::read_dir(folder_date_path) + }) + .filter_map_ok(|files_inside_folder_on_date| { + let response_time_files = files_inside_folder_on_date + .filter_ok(|entry| { + let entry_file_name_as_str = entry + .file_name() + .into_string() + .expect("Failed to get filename as a String"); + + entry_file_name_as_str.ends_with("response_time.csv") + }) + .filter_map(|value| { + if value.is_ok() { + return Some(value.unwrap()); + } + None + }) + .map(|file_path| { + println!("Processing response time file: {:?}", file_path.path()); + + let mut rdr = csv::ReaderBuilder::new() + .delimiter(b';') + .has_headers(true) + .from_reader(std::fs::File::open(file_path.path()).unwrap()); + + let records: Vec = rdr + .deserialize() + .filter_map(Result::ok) + .collect(); + + records + }) + .flat_map(|records| records) + .collect_vec(); + + Some(response_time_files) + }) + .filter_map(|res| { + if res.is_ok() { + return Some(res.unwrap()); + } + return None; + }) + .flat_map(|records| records) + .collect_vec(); + + // Salvar response times consolidados do mês + if !response_times_data.is_empty() { + let response_time_file_path = format!( + "./groupped/{first_day_of_last_month}/response_times_consolidated_{first_day_of_last_month}.csv" + ); + + let mut wtr = csv::WriterBuilder::new() + .delimiter(b';') + .from_path(&response_time_file_path) + .expect("Failed to create response times CSV"); + + // Escrever cabeçalho + wtr.write_record(&["NOME", "ID_TALK", "TEMPO DE RESPOSTA", "TRANFERENCIA PELO BOT", "PRIMEIRA RESPOSTA DO AGENTE"]) + .expect("Failed to write header"); + + for record in &response_times_data { + wtr.write_record(&[ + &record.NOME, + &record.ID_TALK, + &record.TEMPO_DE_RESPOSTA.to_string(), + &record.TRANFERENCIA_PELO_BOT, + &record.PRIMEIRA_RESPOSTA_DO_AGENTE, + ]).expect("Failed to write record"); + } + + wtr.flush().expect("Failed to flush writer"); + + // Calcular estatísticas mensais + let total_records = response_times_data.len(); + let avg_response_time = response_times_data.iter() + .map(|r| r.TEMPO_DE_RESPOSTA) + .sum::() as f32 / total_records as f32; + + let min_response_time = response_times_data.iter() + .map(|r| r.TEMPO_DE_RESPOSTA) + .min() + .unwrap_or(0); + + let max_response_time = response_times_data.iter() + .map(|r| r.TEMPO_DE_RESPOSTA) + .max() + .unwrap_or(0); + + println!("Response times consolidated successfully for month {}!", first_day_of_last_month); + println!("Total records: {}", total_records); + println!("Average response time: {:.2} seconds", avg_response_time); + println!("Min response time: {} seconds", min_response_time); + println!("Max response time: {} seconds", max_response_time); + } else { + println!("No response time data found for the month {}.", first_day_of_last_month); + } +// --- FIM DA ADIÇÃO --- + + zip_directory_util::zip_directory_util::zip_source_dir_to_dst_file( + std::path::Path::new(&format!("./groupped/{first_day_of_last_month}")), + std::path::Path::new(&format!("./groupped/{first_day_of_last_month}.zip")), + ); + + let recipients = "Wilson da Conceição Oliveira , nicolas.borges@nova.net.br>"; + println!("Trying to send mail... {recipients}"); + send_mail_util::send_mail_util::send_email( + &format!("Relatório agrupado dos atendimentos da fila do Financenrio NVL2 do mês {first_day_of_last_month}"), + &BOT_EMAIL, + &BOT_EMAIL_PASSWORD, + recipients, + &format!("./groupped/{first_day_of_last_month}.zip"), + ); +} diff --git a/src/groupped_repport_weekly.rs b/src/groupped_repport_weekly.rs new file mode 100644 index 0000000..f26b01b --- /dev/null +++ b/src/groupped_repport_weekly.rs @@ -0,0 +1,493 @@ +use std::fmt::Debug; + +use itertools::Itertools; +use polars::prelude::*; +use reqwest; +use std::env; +use std::time::Duration; + +use csv; + +pub mod send_mail_util; +pub mod zip_directory_util; + +#[derive(Debug, serde::Deserialize)] +struct CsvHeader { + CATEGORIA: String, + PONTOS: Option, +} + +#[derive(Debug, serde::Deserialize)] +struct CsvEvaluation { + APRESENTAÇÃO: u8, + CONFIRMAÇÃO_DE_EMAIL: u8, + CONFIRMAÇÃO_DE_TELEFONE: u8, + PROTOCOLO: u8, + USO_DO_PORTUGUÊS: u8, + PACIÊNCIA_E_EDUCAÇÃO: u8, + DISPONIBILIDADE: u8, + CONHECIMENTO_TÉCNICO: u8, + DIDATISMO: u8, + ID_TALK: String, +} + +//inclusão de estrutura para agrupar o response_time.cvs +#[derive(Debug, serde::Deserialize)] +struct ResponseTimeRecord { + NOME: String, + ID_TALK: String, + #[serde(rename = "TEMPO DE RESPOSTA")] + TEMPO_DE_RESPOSTA: u32, + #[serde(rename = "TRANFERENCIA PELO BOT")] + TRANFERENCIA_PELO_BOT: String, + #[serde(rename = "PRIMEIRA RESPOSTA DO AGENTE")] + PRIMEIRA_RESPOSTA_DO_AGENTE: String, +} +//fim da inclusão + +fn main() { + match dotenv::dotenv().ok() { + Some(_) => println!("Environment variables loaded from .env file"), + None => eprintln!("Failed to load .env file, using defaults"), + } + + // Read environment variables + let OLLAMA_URL = env::var("OLLAMA_URL").unwrap_or("localhost".to_string()); + let OLLAMA_PORT = env::var("OLLAMA_PORT") + .unwrap_or("11432".to_string()) + .parse::() + .unwrap_or(11432); + let OLLAMA_AI_MODEL_DATA_SANITIZATION = env::var("OLLAMA_AI_MODEL_DATA_SANITIZATION") + .expect("Missing environment variable OLLAMA_AI_MODEL_DATA_SANITIZATION"); + let BOT_EMAIL = env::var("BOT_EMAIL").expect("BOT_EMAIL has not been set!"); + let BOT_EMAIL_PASSWORD = + env::var("BOT_EMAIL_PASSWORD").expect("BOT_EMAIL_PASSWORD has not been set!"); + + let ip_address = ipaddress::IPAddress::parse(OLLAMA_URL.to_string()); + let OLLAMA_SANITIZED_IP = match ip_address { + Ok(ip) => { + if ip.is_ipv4() { + OLLAMA_URL.clone() + } else { + format!("[{}]", OLLAMA_URL.clone()) + } + } + Err(e) => OLLAMA_URL.clone(), + }; + + // Get the current day in the format YYYY-MM-DD + let current_date = chrono::Local::now(); + let formatted_date = current_date.format("%Y-%m-%d").to_string(); + + let current_date = chrono::Local::now(); + let first_day_of_current_week = current_date + .date_naive() + .week(chrono::Weekday::Sun) + .first_day(); + let current_date_minus_one_week = first_day_of_current_week + .checked_sub_days(chrono::Days::new(1)) + .expect("Failed to subtract one day"); + let first_day_of_last_week = current_date_minus_one_week + .week(chrono::Weekday::Sun) + .first_day(); + let last_day_of_last_week = current_date_minus_one_week + .week(chrono::Weekday::Sun) + .last_day(); + + let previous_week_folder_names = std::fs::read_dir(std::path::Path::new("./evaluations")) + .expect("Failed to read directory ./evaluations") + .filter_map_ok(|entry| { + if entry.metadata().unwrap().is_dir() { + Some(entry.file_name()) + } else { + None + } + }) + .filter_map_ok(|entry_string_name| { + let regex_match_date = + regex::Regex::new(r"(\d{4}-\d{2}-\d{2})").expect("Failed to build regex"); + + let filename = entry_string_name.to_str().unwrap(); + let matches_find = regex_match_date.find(filename); + + match matches_find { + Some(found) => { + let date = chrono::NaiveDate::parse_from_str(found.as_str(), "%Y-%m-%d"); + return Some((date.unwrap().week(chrono::Weekday::Sun), entry_string_name)); + } + None => { + return None; + } + }; + }) + .filter_map_ok(|(week, directory_string)| { + let first_day_of_week_in_folder_name = week.first_day(); + + if first_day_of_last_week == first_day_of_week_in_folder_name { + return Some(directory_string); + } + return None; + }) + .filter_map(|value| { + if value.is_ok() { + return Some(value.unwrap()); + } else { + return None; + } + }) + .sorted() + .collect_vec(); + + println!("{:?}", previous_week_folder_names); + + let prompt_data_sanitization = std::fs::read_to_string("./PROMPT_DATA_SANITIZATION.txt") + .expect("Failed to read PROMPT_DATA_SANITIZATION.txt"); + let client = reqwest::blocking::Client::new(); + + let groupped_values = previous_week_folder_names + .iter() + .map(|folder_name| { + let folder_base_path = std::path::Path::new("./evaluations"); + let folder_date_path = folder_base_path.join(folder_name); + std::fs::read_dir(folder_date_path) + }) + .filter_map_ok(|files_inside_folder_on_date| { + let groupped_by_user_on_day = files_inside_folder_on_date + .filter_ok(|entry| { + let entry_file_name_as_str = entry + .file_name() + .into_string() + .expect("Failed to get filename as a String"); + + entry_file_name_as_str.ends_with(".csv") + && !entry_file_name_as_str.contains("response_time.csv") + }) + .filter_map(|value| { + if value.is_ok() { + return Some(value.unwrap()); + } + None + }) + .map(|file_name_csv| { + println!("{:?}", file_name_csv.path()); + let file_contents = std::fs::read_to_string(file_name_csv.path()) + .expect("Failed to read CSV file"); + + let ollama_api_request = client + .post(format!( + "http://{OLLAMA_SANITIZED_IP}:{OLLAMA_PORT}/api/generate" + )) + .body( + serde_json::json!({ + "model": OLLAMA_AI_MODEL_DATA_SANITIZATION, + "prompt": format!("{prompt_data_sanitization} \n{file_contents}"), + "temperature": 0.0, // Get predictable and reproducible output + "stream": false, + }) + .to_string(), + ); + + let result = ollama_api_request.timeout(Duration::from_secs(3600)).send(); + + match result { + Ok(response) => { + println!("Response: {:?}", response); + let response_json = response + .json::() + .expect("Failed to deserialize response to JSON"); + let ai_response = response_json["response"] + .as_str() + .expect("Failed to get AI response as string"); + + let ai_response = ai_response.to_string(); + + let ai_response = if let Some(resp) = ai_response + .strip_prefix(" ") + .unwrap_or(&ai_response) + .strip_prefix("```csv\n") + { + resp.to_string() + } else { + ai_response + }; + let ai_response = if let Some(resp) = ai_response + .strip_suffix(" ") + .unwrap_or(&ai_response) + .strip_suffix("```") + { + resp.to_string() + } else { + ai_response + }; + + return Ok((ai_response, file_name_csv)); + } + Err(error) => { + println!("Error {error}"); + return Err(error); + } + }; + }) + .filter_map_ok(|(ai_repsonse, file_path_csv)| { + let mut reader = csv::ReaderBuilder::new() + .has_headers(true) + .delimiter(b';') + .from_reader(ai_repsonse.as_bytes()); + + let mut deserialized_iter = reader.deserialize::(); + + let mut columns = deserialized_iter + .filter_ok(|value| value.PONTOS.is_some()) + .map_ok(|value| { + let col = + Column::new(value.CATEGORIA.into(), [value.PONTOS.unwrap() as u32]); + col + }) + .filter_map(|value| { + if value.is_ok() { + return Some(value.unwrap()); + } + None + }) + .collect_vec(); + + if columns.len() != 9 { + return None; + } + + // Parse id talk from file_path + // filename example is: CC - Erraoander Quintana - 515578 - 20251020515578.csv + // id talk is the last information, so in the example is: 20251020515578 + let regex_filename = + regex::Regex::new(r"(CC - )((\w+\s*)+) - (\d+) - (\d+).csv").unwrap(); + + let filename = file_path_csv + .file_name() + .into_string() + .expect("Failed to convert file name as Rust &str"); + let found_regex_groups_in_filename = regex_filename + .captures(filename.as_str()) + .expect("Failed to do regex capture"); + + let user_name = found_regex_groups_in_filename + .get(2) + .expect("Failed to get the id from regex maches"); + let talk_id = found_regex_groups_in_filename + .get(5) + .expect("Failed to get the id from regex maches"); + + let excelence_percentual = columns + .iter() + .map(|col| col.as_materialized_series().u32().unwrap().sum().unwrap()) + .sum::() as f32 + / columns.iter().len() as f32 + * 100.0; + columns.push(Column::new( + "PERCENTUAL DE EXELENCIA".into(), + [format!("{excelence_percentual:.2}")], + )); + + columns.push(Column::new("ID_TALK".into(), [talk_id.clone().as_str()])); + + let df = polars::frame::DataFrame::new(columns) + .expect("Failed to concatenate into a dataframe"); + + // return a tuple with the dataframe and the user name, so it can be correctly merged after + return Some((user_name.as_str().to_owned(), df)); + }) + .filter_map(|res| { + if res.is_ok() { + return Some(res.unwrap()); + } + return None; + }) + .into_group_map() + .into_iter() + .map(|(name, eval_dataframe_vec)| { + let groupped_df = eval_dataframe_vec + .iter() + .cloned() + .reduce(|acc, e| acc.vstack(&e).unwrap()) + .expect("Failed to concatenate dataframes"); + (name, groupped_df) + }) + .into_group_map(); + + dbg!(&groupped_by_user_on_day); + return Some(groupped_by_user_on_day); + }) + .filter_map(|res| { + if res.is_ok() { + return Some(res.unwrap()); + } + return None; + }) + .reduce(|mut acc, mut e| { + e.iter_mut().for_each(|(key, val)| { + if acc.contains_key(key) { + acc.get_mut(key) + .expect("Failed to obtain key that should already be present") + .append(val); + } else { + acc.insert(key.to_owned(), val.to_owned()); + } + }); + acc + }) + .and_then(|groupped_hashmap_df| { + let result = groupped_hashmap_df + .iter() + .map(|(key, val)| { + let dfs = val + .iter() + .cloned() + .reduce(|acc, e| acc.vstack(&e).unwrap()) + .expect("Failed to concatenate dataframes"); + (key.clone(), dfs) + }) + .collect_vec(); + return Some(result); + }); + + // Setup groupped folder + if !std::fs::exists(format!("./groupped/")).unwrap() { + std::fs::create_dir(format!("./groupped")).expect("Failed to create directory") + } + + // Setup previous week folder + if !std::fs::exists(format!( + "./groupped/{first_day_of_last_week} - {last_day_of_last_week}" + )) + .unwrap() + { + std::fs::create_dir(format!( + "./groupped/{first_day_of_last_week} - {last_day_of_last_week}" + )) + .expect("Failed to create directory") + } + + match groupped_values { + Some(mut val) => { + val.iter_mut().for_each(|(agent, groupped_evaluations)| { + let mut save_file_csv = std::fs::File::create(format!( + "./groupped/{first_day_of_last_week} - {last_day_of_last_week}/{agent}.csv" + )) + .expect("Could not create csv file for saving"); + CsvWriter::new(&mut save_file_csv) + .include_header(true) + .with_separator(b';') + .finish(groupped_evaluations) + .expect("Failed to save Groupped DataFrame to CSV File"); + }); + } + None => {} + } + +//inclusão nova para agrupar o response_time.csv + // Processar response_time.csv separadamente + let response_times_data = previous_week_folder_names + .iter() + .map(|folder_name| { + let folder_base_path = std::path::Path::new("./evaluations"); + let folder_date_path = folder_base_path.join(folder_name); + std::fs::read_dir(folder_date_path) + }) + .filter_map_ok(|files_inside_folder_on_date| { + let response_time_files = files_inside_folder_on_date + .filter_ok(|entry| { + let entry_file_name_as_str = entry + .file_name() + .into_string() + .expect("Failed to get filename as a String"); + + entry_file_name_as_str.ends_with("response_time.csv") + }) + .filter_map(|value| { + if value.is_ok() { + return Some(value.unwrap()); + } + None + }) + .map(|file_path| { + println!("Processing response time file: {:?}", file_path.path()); + + let mut rdr = csv::ReaderBuilder::new() + .delimiter(b';') + .has_headers(true) + .from_reader(std::fs::File::open(file_path.path()).unwrap()); + + let records: Vec = rdr + .deserialize() + .filter_map(Result::ok) + .collect(); + + records + }) + .flat_map(|records| records) + .collect_vec(); + + Some(response_time_files) + }) + .filter_map(|res| { + if res.is_ok() { + return Some(res.unwrap()); + } + return None; + }) + .flat_map(|records| records) + .collect_vec(); + // Salvar response times consolidados + if !response_times_data.is_empty() { + let response_time_file_path = format!( + "./groupped/{first_day_of_last_week} - {last_day_of_last_week}/response_times_consolidated.csv" + ); + + let mut wtr = csv::WriterBuilder::new() + .delimiter(b';') + .from_path(response_time_file_path) + .expect("Failed to create response times CSV"); + + // Escrever cabeçalho + wtr.write_record(&["NOME", "ID_TALK", "TEMPO DE RESPOSTA", "TRANFERENCIA PELO BOT", "PRIMEIRA RESPOSTA DO AGENTE"]) + .expect("Failed to write header"); + + for record in response_times_data { + wtr.write_record(&[ + &record.NOME, + &record.ID_TALK, + &record.TEMPO_DE_RESPOSTA.to_string(), + &record.TRANFERENCIA_PELO_BOT, + &record.PRIMEIRA_RESPOSTA_DO_AGENTE, + ]).expect("Failed to write record"); + } + + wtr.flush().expect("Failed to flush writer"); + println!("Response times consolidated successfully!"); + } else { + println!("No response time data found for the period."); + } +// --- FIM DA ADIÇÃO --- + +//fim da inclusão + + zip_directory_util::zip_directory_util::zip_source_dir_to_dst_file( + std::path::Path::new(&format!( + "./groupped/{first_day_of_last_week} - {last_day_of_last_week}" + )), + std::path::Path::new(&format!( + "./groupped/{first_day_of_last_week} - {last_day_of_last_week}.zip" + )), + ); + + let recipients = "Wilson da Conceição Oliveira , nicolas.borges@nova.net.br>"; + println!("Trying to send mail... {recipients}"); + send_mail_util::send_mail_util::send_email( + &format!( + "Relatório agrupado dos atendimentos da fila do Financeiro NVL2 da semana {first_day_of_last_week} - {last_day_of_last_week}" + ), + &BOT_EMAIL, + &BOT_EMAIL_PASSWORD, + recipients, + &format!("./groupped/{first_day_of_last_week} - {last_day_of_last_week}.zip"), + ); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5c32e88 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,680 @@ +use std::{any::Any, env, fmt::format, iter, time::Duration}; + +use chrono::{self, Timelike}; +use dotenv; +use ipaddress; +use itertools::{self, Itertools}; +use lettre::{self, message}; +use reqwest; +use serde_json::{self, json}; + +use std::io::prelude::*; + +pub mod send_mail_util; +pub mod zip_directory_util; + +fn main() -> anyhow::Result<()> { + match dotenv::dotenv().ok() { + Some(_) => println!("Environment variables loaded from .env file"), + None => eprintln!("Failed to load .env file, using defaults"), + } + + // Read environment variables + let OLLAMA_URL = env::var("OLLAMA_URL").unwrap_or("localhost".to_string()); + let OLLAMA_PORT = env::var("OLLAMA_PORT") + .unwrap_or("11432".to_string()) + .parse::() + .unwrap_or(11432); + let PIPERUN_API_URL = env::var("PIPERUN_API_URL").expect("PIPERUN_API_URL has not been set!"); + let PIPERUN_CLIENT_ID = env::var("PIPERUN_CLIENT_ID") + .expect("PIPERUN_CLIENT_ID has not been set!") + .parse::() + .unwrap_or(0); + let PIPERUN_CLIENT_SECRET = + env::var("PIPERUN_CLIENT_SECRET").expect("PIPERUN_CLIENT_SECRET has not been set!"); + let PIPERUN_BOT_USERNAME = + env::var("PIPERUN_BOT_USERNAME").expect("PIPERUN_BOT_USERNAME has not been set!"); + let PIPERUN_BOT_PASSWORD = + env::var("PIPERUN_BOT_PASSWORD").expect("PIPERUN_BOT_PASSWORD has not been set!"); + let OLLAMA_AI_MODEL = env::var("OLLAMA_AI_MODEL").expect("OLLAMA_AI_MODEL has not been set!"); + let MINIMUM_NUMBER_OF_MESSAGES_TO_EVALUATE = env::var("MINIMUM_NUMBER_OF_MESSAGES_TO_EVALUATE") + .expect("MINIMUM_NUMBER_OF_MESSAGES_TO_EVALUATE has not been set!") + .parse::() + .unwrap_or(10); + let MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE = + env::var("MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE") + .expect("MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE has not been set!") + .parse::() + .unwrap_or(12); + let BOT_EMAIL = env::var("BOT_EMAIL").expect("BOT_EMAIL has not been set!"); + let BOT_EMAIL_PASSWORD = + env::var("BOT_EMAIL_PASSWORD").expect("BOT_EMAIL_PASSWORD has not been set!"); + + // Print the configuration + println!("OLLAMA_URL: {}", OLLAMA_URL); + println!("OLLAMA_PORT: {}", OLLAMA_PORT); + println!("OLLAMA_AI_MODEL: {}", OLLAMA_AI_MODEL); + println!("PIPERUN_API_URL: {}", PIPERUN_API_URL); + println!("PIPERUN_CLIENT_ID: {}", PIPERUN_CLIENT_ID); + println!("PIPERUN_CLIENT_SECRET: {}", PIPERUN_CLIENT_SECRET); + println!("PIPERUN_BOT_USERNAME: {}", PIPERUN_BOT_USERNAME); + println!("PIPERUN_BOT_PASSWORD: {}", PIPERUN_BOT_PASSWORD); + + let ip_address = ipaddress::IPAddress::parse(OLLAMA_URL.to_string()); + let OLLAMA_SANITIZED_IP = match ip_address { + Ok(ip) => { + if ip.is_ipv4() { + OLLAMA_URL.clone() + } else { + format!("[{}]", OLLAMA_URL.clone()) + } + } + Err(e) => OLLAMA_URL.clone(), + }; + + // Send the authentication request + let client = reqwest::blocking::Client::new(); + + let auth_request = client + .post(format!("https://{}/oauth/token", PIPERUN_API_URL)) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .body( + serde_json::json!({ + "grant_type": "password", + "client_id": PIPERUN_CLIENT_ID, + "client_secret": PIPERUN_CLIENT_SECRET, + "username": PIPERUN_BOT_USERNAME, + "password": PIPERUN_BOT_PASSWORD, + }) + .to_string(), + ); + + println!("Sending authentication request to Piperun API..."); + println!("{:?}", auth_request); + + let response = auth_request.send(); + let access_token = match response { + Ok(resp) => { + if resp.status().is_success() { + let json: serde_json::Value = resp.json()?; + // println!("Authentication successful: {:?}", json); + + // Extract the access token + if let Some(access_token) = json.get("access_token") { + println!("Access Token: {}", access_token); + access_token + .as_str() + .expect("Failed to get token") + .to_string() + } else { + eprintln!("Access token not found in response"); + panic!("Failed to retrieve access token from Piperun API"); + } + } else { + eprintln!("Authentication failed: {}", resp.status()); + let json: serde_json::Value = resp.json()?; + eprintln!("Response body: {:?}", json); + + panic!("Failed to authenticate with Piperun API"); + } + } + Err(e) => { + eprintln!("Error sending authentication request: {}", e); + panic!("Failed to send authentication request to Piperun API"); + } + }; + + // Get the current day in the format YYYY-MM-DD + let current_date = chrono::Local::now(); + let formatted_date = current_date.format("%Y-%m-%d").to_string(); + + println!("Current date: {}", formatted_date); + + // Get the day before the current date + let day_before = current_date + .checked_sub_signed(chrono::Duration::days(1)) + .expect("Failed to get the day before"); + let formatted_day_before = day_before.format("%Y-%m-%d").to_string(); + + println!("Day before: {}", formatted_day_before); + + let day_before_at_midnight = day_before + .with_hour(0) + .unwrap() + .with_minute(0) + .unwrap() + .with_second(0) + .unwrap(); + let formatted_day_before_at_midnight = + day_before_at_midnight.format("%Y-%m-%d %H:%M").to_string(); + + let day_before_at_23_59_59 = day_before + .with_hour(23) + .unwrap() + .with_minute(59) + .unwrap() + .with_second(59) + .unwrap(); + let formatted_day_before_at_23_59_59 = + day_before_at_23_59_59.format("%Y-%m-%d %H:%M").to_string(); + + println!( + "Day before at midnight: {}, Day before at 23:59:59: {}", + formatted_day_before_at_midnight, formatted_day_before_at_23_59_59 + ); + + let formatted_day_before = day_before_at_midnight.format("%Y-%m-%d").to_string(); + + // Create a folder named with the day_before + if !std::fs::exists(format!("./evaluations/{formatted_day_before}")).unwrap() { + std::fs::create_dir(format!("./evaluations/{formatted_day_before}")) + .expect("Failed to create directory") + } + + // Create the response time folder + if !std::fs::exists(format!( + "./evaluations/{formatted_day_before}/response_time.csv" + )) + .unwrap() + { + let mut response_time_file = std::fs::File::create_new(format!( + "./evaluations/{formatted_day_before}/response_time.csv" + )) + .expect("Failed to response_time.csv"); + } + + // Read system prompt + let prompt = std::fs::read_to_string("PROMPT.txt").unwrap(); + let filter_file_contents = std::fs::read_to_string("FILTER.txt").unwrap_or(String::new()); + let filter_keywords = filter_file_contents + .split("\n") + .filter(|keyword| !keyword.is_empty()) + .collect::>(); + + let talks_array = get_piperun_chats_on_date( + &PIPERUN_API_URL, + &client, + &access_token, + formatted_day_before_at_midnight, + formatted_day_before_at_23_59_59, + ); + + println!("Number of consolidated talks: {}", talks_array.len()); + + let talk_ids = talks_array + .iter() + .cloned() + .map(|value| { + serde_json::from_value::(value).expect("Failed to parse the JSON") + ["id"] + .clone() + .to_string() + }) + .collect::>(); + + println!("IDS {:?}", talk_ids); + + // Gather messages and apply filtering + let filtered_chats = talk_ids + .iter() + .cloned() + .map(|talk_id| { + let talk_id_get_request = client + .get(format!("https://{}/api/talk_histories", PIPERUN_API_URL)) + .bearer_auth(&access_token) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .query(&[ + ("talk_id", talk_id), + ("type", "a".to_string()), + ("only_view", "1".to_string()), + ]); + + let talk_id_get_result = talk_id_get_request.send(); + + return talk_id_get_result; + }) + .filter_map_ok(|result| { + let json = result + .json::() + .expect("Failed to deserialize response to JSON") + .to_owned(); + let talk_histories = &json["talk_histories"]; + let data = &talk_histories["data"]; + + // Filter chats that have very few messages + let talk_lenght = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .len(); + if talk_lenght < MINIMUM_NUMBER_OF_MESSAGES_TO_EVALUATE { + return None; + } + + // Filter chats that have less that specified ammount of talks with support agent form the last queue transfer + let found = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .into_iter() + .enumerate() + .find(|(pos, message_object)| { + let message = message_object["message"] + .as_str() + .expect("Failed to decode message as string"); + let found = message.find( + "Atendimento transferido para a fila [NovaNet -> Atendimento -> Financeiro NVL2]", + ); + found.is_some() + }); + + match found { + None => { + return None; + } + Some(pos) => { + let pos_found = pos.0; + if pos_found < MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE { + return None; + } + } + }; + + // Filter Bot finished chats + if json["agent"]["user"]["name"] + .as_str() + .unwrap_or("unknown_user") + == "PipeBot" + { + return None; + } + + // Filtra os chats que nao foram encerrados + if json["finished_at"].to_string() + == "null" + { + return None; + } + + // Apply keyword based filtering + let filter_keywords_found = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .into_iter() + .any(|message_object| { + let message = message_object["message"] + .as_str() + .expect("Failed to decode message as string"); + let found1 = filter_keywords.iter().any(|keyword| { + message + .to_uppercase() + .find(&keyword.to_uppercase()) + .is_some() + }); + let found2 = message_object["is_template"] + .as_bool() + .unwrap_or(true); + let found = found1 || found2; + found + }); + + if filter_keywords_found { + return None; + } + + return Some(json); + }); + + // Calculate the response time in seconds + let response_time = filtered_chats + .clone() + .map(|messages| { + let json = messages.unwrap(); + let talk_histories = &json["talk_histories"]; + + // dbg!(&talk_histories); + + // talk_histories.as_array().unwrap().into_iter().enumerate().for_each(|(pos, message_obj)|{println!("{}: {}", pos, message_obj["message"])}); + + // find the bot transfer message + let bot_transfer_message = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .into_iter() + .enumerate() + .filter(|(pos, message_object)| { + let user_name = message_object["user"]["name"] + .as_str() + .expect("Failed to decode message as string"); + user_name == "PipeBot".to_string() + }) + .find(|(pos, message_object)| { + let message = message_object["message"] + .as_str() + .expect("Failed to decode message as string"); + //let found = message.find("Atendimento transferido para a fila [NovaNet -> Atendimento -> Financeiro NVL2]"); + let found = message.find("Atendimento entregue da fila de espera para o agente [FIN - "); + found.is_some() + }); + + // Find first agent message sent after the last bot message + let (pos, transfer_message) = + bot_transfer_message.expect("Failed to get the transfer bot message position"); + + let msg = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .into_iter() + .take(pos) + .rev() + .filter(|message| { + message["type"] == "out".to_string() + // && message["user"]["name"] != "PipeBot".to_string() + && message["user"]["name"].as_str().map_or(false, |name| name.starts_with("FIN -")) + }) + .take(1) + .collect_vec(); + + //if msg[0] { + // return None; + //} + let agent_first_message = msg[0]; + + // Calculate time difference between bot message and agent message + let date_user_message_sent = agent_first_message["sent_at"].as_str().unwrap(); + + let format = "%Y-%m-%d %H:%M:%S"; + + let date_user_message_sent_parsed = + match chrono::NaiveDateTime::parse_from_str(date_user_message_sent, format) { + Ok(dt) => dt, + Err(e) => { + println!("Error parsing DateTime: {}", e); + panic!("Failed parsing date") + } + }; + + let date_transfer_message_sent_parsed = match chrono::NaiveDateTime::parse_from_str( + transfer_message["sent_at"].as_str().unwrap(), + format, + ) { + Ok(dt) => dt, + Err(e) => { + println!("Error parsing DateTime: {}", e); + panic!("Failed parsing date") + } + }; + + let response_time = (date_user_message_sent_parsed - date_transfer_message_sent_parsed) + .as_seconds_f32(); + let name = agent_first_message["user"]["name"] + .as_str() + .unwrap() + .to_owned(); + let id = json["tracking_number"].as_str().unwrap_or("").to_owned(); + let bot_transfer_date = date_transfer_message_sent_parsed.to_owned(); + let user_response_date = date_user_message_sent.to_owned(); + println!( + "response_time: {}s", + (date_user_message_sent_parsed - date_transfer_message_sent_parsed) + .as_seconds_f32() + ); + + format!( + "{};{};{};{};{}", + name, id, response_time, bot_transfer_date, user_response_date + ) + }) + .reduce(|acc, e| format!("{}\n{}", acc, e)) + .unwrap_or("".to_string()); + + // return Ok(()); + // Open file and write to it + let header = "NOME;ID_TALK;TEMPO DE RESPOSTA;TRANFERENCIA PELO BOT;PRIMEIRA RESPOSTA DO AGENTE"; + let mut response_time_file = std::fs::OpenOptions::new() + .write(true) + .open(format!( + "./evaluations/{formatted_day_before}/response_time.csv" + )) + .expect("Failed to open response time file for write"); + response_time_file + .write_all(format!("{header}\n{response_time}").as_bytes()) + .expect("Failed to write header to file"); + + filtered_chats.clone().skip(0).for_each(|result| { + let json = result.unwrap(); + let talk_histories = &json["talk_histories"]; + let data = &talk_histories["data"]; + + let talk = talk_histories + .as_array() + .expect("Wrong message type received from talk histories") + .iter() + .rev() + .map(|message_object| { + let new_json_filtered = format!( + "{{ + message: {}, + sent_at: {}, + type: {}, + user_name: {} +}}", + message_object["message"], + message_object["sent_at"], + message_object["type"], + message_object["user"]["name"] + ); + // println!("{}", new_json_filtered); + new_json_filtered + }) + .reduce(|acc, e| format!("{acc}\n{e}")) + .expect("Error extracting talk"); + + println!("{prompt}\n {talk}"); + + let ollama_api_request = client + .post(format!( + "http://{OLLAMA_SANITIZED_IP}:{OLLAMA_PORT}/api/generate" + )) + .body( + serde_json::json!({ + "model": OLLAMA_AI_MODEL, + "prompt": format!("{prompt} \n{talk}"), + // "options": serde_json::json!({"temperature": 0.1}), + "stream": false, + }) + .to_string(), + ); + + let result = ollama_api_request.timeout(Duration::from_secs(3600)).send(); + + match result { + Ok(response) => { + println!("Response: {:?}", response); + let response_json = response + .json::() + .expect("Failed to deserialize response to JSON"); + println!("{}", response_json); + let ai_response = response_json["response"] + .as_str() + .expect("Failed to get AI response as string"); + println!("AI Response: {}", ai_response); + + let csv_response = ai_response.replace("```csv\n", "").replace("```", ""); + + // Save the CSV response to a file + + let user_name = &json["agent"]["user"]["name"] + .as_str() + .unwrap_or("unknown_user"); + let talk_id = &json["id"].as_u64().unwrap_or(0); + let tracking_number = &json["tracking_number"].as_str().unwrap_or(""); + std::fs::write( + format!( + "./evaluations/{}/{} - {} - {}.csv", + formatted_day_before, user_name, talk_id, tracking_number + ), + csv_response, + ) + .expect("Unable to write file"); + std::fs::write( + format!( + "./evaluations/{}/{} - {} - {} - prompt.txt", + formatted_day_before, user_name, talk_id, tracking_number + ), + format!("{prompt} \n{talk}"), + ) + .expect("Unable to write file"); + } + Err(error) => { + println!("Error {error}"); + } + }; + }); + + // Compress folder into zip + let source_dir_str = format!("./evaluations/{formatted_day_before}"); + let output_zip_file_str = format!("./evaluations/{formatted_day_before}.zip"); + let source_dir = std::path::Path::new(source_dir_str.as_str()); + let output_zip_file = std::path::Path::new(output_zip_file_str.as_str()); + zip_directory_util::zip_directory_util::zip_source_dir_to_dst_file(source_dir, output_zip_file); + + // Send folder to email + let recipients = "Wilson da Conceição Oliveira , nicolas.borges@nova.net.br"; +// let recipients = "Wilson da Conceição Oliveira , nicolas.borges@nova.net.br"; + println!("Trying to send email... Recipients {recipients}"); + + send_mail_util::send_mail_util::send_email( + &format!("Avaliacao atendimentos da fila Financeiro NVL2 do dia {formatted_day_before}"), + &BOT_EMAIL, + &BOT_EMAIL_PASSWORD, + recipients, + &output_zip_file_str, + ); + + return Ok(()); +} + +fn get_piperun_chats_on_date( + PIPERUN_API_URL: &String, + client: &reqwest::blocking::Client, + access_token: &String, + formatted_day_before_at_midnight: String, + formatted_day_before_at_23_59_59: String, +) -> Vec { + let start_of_talk_code: String = "talk_start".to_string(); + let support_queue_id: String = "16".to_string(); + + // API V2 + let report_type = "consolidated".to_string(); + let page = "1".to_string(); + let per_page = "15".to_string(); + + let talks_request = client + .get(format!("https://{}/api/v2/reports/talks", PIPERUN_API_URL)) + .bearer_auth(access_token) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .query(&[ + ("page", page.clone()), + ("perPage", per_page.clone()), + ("report_type", report_type.clone()), + ("start_date", formatted_day_before_at_midnight.clone()), + ("end_date", formatted_day_before_at_23_59_59.clone()), + ("date_range_type", start_of_talk_code.clone()), + ("queue_id[]", support_queue_id.clone()), + ]); + + println!("Sending request for consolidated talks... {talks_request:?}"); + let talks_response = talks_request.send(); + + let json_response = match talks_response { + Ok(resp) => { + if resp.status().is_success() { + let json: serde_json::Value = resp.json().unwrap(); + json + } else { + eprintln!("Failed to get consolidated talks: {}", resp.status()); + let json: serde_json::Value = resp.json().unwrap(); + eprintln!("Response body: {:?}", json); + panic!("Failed to retrieve consolidated talks from Piperun API"); + } + } + Err(e) => { + eprintln!("Error: {e}"); + panic!("Failed to send the request for talks to PipeRUN API"); + } + }; + + let mut aggregated_talks = json_response["data"] + .as_array() + .expect("Failed to parse messages as array") + .to_owned(); + + let current_page = json_response["current_page"] + .as_i64() + .expect("Failed to obtain current page number"); + let last_page = json_response["last_page"] + .as_i64() + .expect("Failed to obtain current page number"); + + if current_page == last_page { + return aggregated_talks; + } + + let mut all_other_messages = (current_page..last_page) + .into_iter() + .map(|page| { + let page_to_request = page + 1; + let talks_request = client + .get(format!("https://{}/api/v2/reports/talks", PIPERUN_API_URL)) + .bearer_auth(access_token) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .query(&[ + ("page", page_to_request.to_string()), + ("perPage", per_page.clone()), + ("report_type", report_type.clone()), + ("start_date", formatted_day_before_at_midnight.clone()), + ("end_date", formatted_day_before_at_23_59_59.clone()), + ("date_range_type", start_of_talk_code.clone()), + ("queue_id[]", support_queue_id.clone()), + ]); + + println!("Sending request for consolidated talks... {talks_request:?}"); + let talks_response = talks_request.send(); + + let json_response = match talks_response { + Ok(resp) => { + if resp.status().is_success() { + let json: serde_json::Value = resp.json().unwrap(); + json + } else { + eprintln!("Failed to get consolidated talks: {}", resp.status()); + let json: serde_json::Value = resp.json().unwrap(); + eprintln!("Response body: {:?}", json); + panic!("Failed to retrieve consolidated talks from Piperun API"); + } + } + Err(e) => { + eprintln!("Error: {e}"); + panic!("Failed to send the request for talks to PipeRUN API"); + } + }; + + let aggregated_talks = json_response["data"] + .as_array() + .expect("Failed to parse messages as array") + .to_owned(); + + return aggregated_talks; + }) + .reduce(|mut this, mut acc| { + acc.append(&mut this); + acc + }) + .expect("Failed to concatenate all vectors of messages"); + + aggregated_talks.append(&mut all_other_messages); + aggregated_talks +} diff --git a/src/send_mail_util.rs b/src/send_mail_util.rs new file mode 100644 index 0000000..e6dbb28 --- /dev/null +++ b/src/send_mail_util.rs @@ -0,0 +1,46 @@ +pub mod send_mail_util { + use lettre::{ + Message, SmtpTransport, Transport, + message::{self, Attachment, Mailboxes, MultiPart, SinglePart, header::ContentType}, + }; + + pub fn send_email( + subject_of_email: &str, + bot_email: &str, + bot_email_password: &str, + to: &str, + zip_file_name: &str, + ) { + let filebody = std::fs::read(zip_file_name).unwrap(); + let content_type = ContentType::parse("application/zip").unwrap(); + let attachment = Attachment::new(zip_file_name.to_string()).body(filebody, content_type); + let mailboxes: Mailboxes = to.parse().unwrap(); + let to_header: message::header::To = mailboxes.into(); + + let email = Message::builder() + .from(format!("PipeRUN bot <{bot_email}>").parse().unwrap()) + .reply_to(format!("PipeRUN bot <{bot_email}>").parse().unwrap()) + .mailbox(to_header) + .subject(format!("{subject_of_email}")) + .multipart( + MultiPart::mixed() + .singlepart( + SinglePart::builder() + .header(ContentType::TEXT_PLAIN) + .body(String::from("Avaliacao dos atendimentos")), + ) + .singlepart(attachment), + ) + .unwrap(); + + // Create the SMTPS transport + let sender = SmtpTransport::from_url(&format!( + "smtps://{bot_email}:{bot_email_password}@mail.nova.net.br" + )) + .unwrap() + .build(); + + // Send the email via remote relay + sender.send(&email).unwrap(); + } +} diff --git a/src/zip_directory_util.rs b/src/zip_directory_util.rs new file mode 100644 index 0000000..c7ae699 --- /dev/null +++ b/src/zip_directory_util.rs @@ -0,0 +1,69 @@ +pub mod zip_directory_util { + + use std::io::prelude::*; + use zip::write::SimpleFileOptions; + + use std::fs::File; + use std::path::Path; + use walkdir::{DirEntry, WalkDir}; + + fn zip_dir( + it: &mut dyn Iterator, + prefix: &Path, + writer: T, + method: zip::CompressionMethod, + ) where + T: Write + Seek, + { + let mut zip = zip::ZipWriter::new(writer); + let options = SimpleFileOptions::default() + .compression_method(method) + .unix_permissions(0o755); + + let prefix = Path::new(prefix); + let mut buffer = Vec::new(); + for entry in it { + let path = entry.path(); + let name = path.strip_prefix(prefix).unwrap(); + let path_as_string = name + .to_str() + .map(str::to_owned) + .expect("Failed to parse path"); + + // Write file or directory explicitly + // Some unzip tools unzip files with directory paths correctly, some do not! + if path.is_file() { + println!("adding file {path:?} as {name:?} ..."); + zip.start_file(path_as_string, options) + .expect("Failed to add file"); + let mut f = File::open(path).unwrap(); + + f.read_to_end(&mut buffer).expect("Failed to read file"); + zip.write_all(&buffer).expect("Failed to write file"); + buffer.clear(); + } else if !name.as_os_str().is_empty() { + // Only if not root! Avoids path spec / warning + // and mapname conversion failed error on unzip + println!("adding dir {path_as_string:?} as {name:?} ..."); + zip.add_directory(path_as_string, options) + .expect("Failed to add directory"); + } + } + zip.finish().expect("Failed to ZIP"); + } + + pub fn zip_source_dir_to_dst_file(src_dir: &Path, dst_file: &Path) { + if !Path::new(src_dir).is_dir() { + panic!("src_dir must be a directory"); + } + + let method = zip::CompressionMethod::Stored; + let path = Path::new(dst_file); + let file = File::create(path).unwrap(); + + let walkdir = WalkDir::new(src_dir); + let it = walkdir.into_iter(); + + zip_dir(&mut it.filter_map(|e| e.ok()), src_dir, file, method); + } +}