From 3af0eab69323a187fbc418de1c6683ca8d8cd9de Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Wed, 15 Oct 2025 12:10:02 -0300 Subject: [PATCH 01/13] chore: started report grouping --- Cargo.toml | 12 ++++++++- PROMPT_DATA_SANITIZATION.txt | 8 ++++++ src/groupped_repport.rs | 49 ++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 PROMPT_DATA_SANITIZATION.txt create mode 100644 src/groupped_repport.rs diff --git a/Cargo.toml b/Cargo.toml index a6f937b..9d4462d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,14 @@ name = "piperun-bot" version = "0.1.0" edition = "2024" +[[bin]] +name = "groupped_repport" +path = "src/groupped_repport.rs" + +[[bin]] +name = "piperun-bot" +path = "src/main.rs" + [dependencies] http = {version = "1.3.1"} dotenv = {version = "0.15.0"} @@ -14,4 +22,6 @@ ipaddress = {version = "0.1.3"} zip = { version = "5.1.1"} walkdir = { version = "2.5.0"} lettre = {version = "0.11.18", features = ["builder"]} -anyhow = { version = "1.0.100"} \ No newline at end of file +anyhow = { version = "1.0.100"} +polars = { version = "0.51.0" } +regex = { version = "1.12.2" } \ No newline at end of file diff --git a/PROMPT_DATA_SANITIZATION.txt b/PROMPT_DATA_SANITIZATION.txt new file mode 100644 index 0000000..4b09605 --- /dev/null +++ b/PROMPT_DATA_SANITIZATION.txt @@ -0,0 +1,8 @@ +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. +Se não for possível padronizar o arquivo de entrada de acordo com as instruções fornecidas a resposta deve ser vazia. +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, ESCLARECIMENTO, TEMPO DE ESPERA +A coluna pontos deve ter apenas os valores 0 ou 1, se no arquivo de entrada estiver incorreto a resposta deve ser vazia. +-------------------------------- diff --git a/src/groupped_repport.rs b/src/groupped_repport.rs new file mode 100644 index 0000000..a57d110 --- /dev/null +++ b/src/groupped_repport.rs @@ -0,0 +1,49 @@ +use itertools::Itertools; +use polars::prelude::*; +use reqwest; +use walkdir; + +fn main() { + let PROMPT = std::fs::read_to_string("./PROMPT_DATA_SANITIZATION.txt").expect("Failed to read the promp for data sanitization"); + + + // 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(); + + 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) => { + chrono::NaiveDateTime::parse_from_str() + return Some(String::from(found)) + }, + None => {return None;} + }; + }) + .for_each(|entry| println!("{:?}", entry)); + // std::fs::ReadDir("./evaluations").for_each(|entry| println!("{:?}", entry)); + + // get folder name for last week evaluations + /* + let evaluations_dir_walk = walkdir::WalkDir::new("./evaluations"); + evaluations_dir_walk.into_iter().for_each( + |entry| { + println!("{:?}", entry.unwrap()); + } + ) + */ +} \ No newline at end of file From cd44d58f6975e9b667ae2cef0265edc004134e01 Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Thu, 16 Oct 2025 12:05:07 -0300 Subject: [PATCH 02/13] chore: obtain previoous week dates --- src/groupped_repport.rs | 57 +++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/groupped_repport.rs b/src/groupped_repport.rs index a57d110..9408415 100644 --- a/src/groupped_repport.rs +++ b/src/groupped_repport.rs @@ -1,3 +1,6 @@ +use std::fmt::Debug; + +use chrono::Datelike; use itertools::Itertools; use polars::prelude::*; use reqwest; @@ -11,7 +14,7 @@ fn main() { let current_date = chrono::Local::now(); let formatted_date = current_date.format("%Y-%m-%d").to_string(); - std::fs::read_dir(std::path::Path::new("./evaluations")).expect("Failed to read directory ./evaluations") + 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()) @@ -28,22 +31,48 @@ fn main() { match matches_find { Some(found) => { - chrono::NaiveDateTime::parse_from_str() - return Some(String::from(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;} }; }) - .for_each(|entry| println!("{:?}", entry)); - // std::fs::ReadDir("./evaluations").for_each(|entry| println!("{:?}", entry)); - - // get folder name for last week evaluations - /* - let evaluations_dir_walk = walkdir::WalkDir::new("./evaluations"); - evaluations_dir_walk.into_iter().for_each( - |entry| { - println!("{:?}", entry.unwrap()); + .filter_map_ok(|(week, directory_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 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); + + // Read CSV files inside folder + + // Use AI to sanitize the data + + // Save into a hashmap, with the user name as key, the date, evaluation + + // Final file should look like +/* +Header: Att1, att2, att3, ... +categoria1 +categoria2 +categoria3 +... + +*/ + + } \ No newline at end of file From 1edb92af50b0059527c4624bedc3a984a184815f Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Mon, 20 Oct 2025 11:10:10 -0300 Subject: [PATCH 03/13] chore: add cargo.lock file --- Cargo.lock | 1752 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 1698 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 200bf90..0948f0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,12 +55,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -72,9 +66,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arbitrary" @@ -85,6 +79,87 @@ 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" @@ -118,11 +193,47 @@ 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" @@ -133,17 +244,52 @@ 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" @@ -154,6 +300,15 @@ 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" @@ -172,17 +327,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.41" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", - "windows-link", + "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]] @@ -205,6 +376,41 @@ dependencies = [ "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" @@ -250,6 +456,16 @@ dependencies = [ "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" @@ -265,6 +481,21 @@ 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" @@ -274,6 +505,72 @@ 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" @@ -284,6 +581,12 @@ dependencies = [ "typenum", ] +[[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" @@ -347,6 +650,12 @@ 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" @@ -394,6 +703,45 @@ dependencies = [ "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" @@ -417,6 +765,12 @@ 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 = "foreign-types" version = "0.3.2" @@ -441,6 +795,31 @@ 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" @@ -457,12 +836,34 @@ 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" @@ -481,8 +882,10 @@ 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", @@ -508,8 +911,10 @@ 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]] @@ -519,9 +924,11 @@ 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]] @@ -530,6 +937,12 @@ 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" @@ -564,6 +977,25 @@ name = "hashbrown" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", + "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" @@ -574,6 +1006,15 @@ 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" @@ -582,7 +1023,7 @@ checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -631,6 +1072,12 @@ 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" @@ -661,6 +1108,7 @@ dependencies = [ "hyper", "hyper-util", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", @@ -848,6 +1296,7 @@ checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.3", + "serde", ] [[package]] @@ -968,24 +1417,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] -name = "liblzma" -version = "0.4.2" +name = "libm" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0791ab7e08ccc8e0ce893f6906eb2703ed8739d8e89b57c0714e71bad09024c8" -dependencies = [ - "liblzma-sys", -] - -[[package]] -name = "liblzma-sys" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736" -dependencies = [ - "cc", - "libc", - "pkg-config", -] +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libz-rs-sys" @@ -1014,18 +1449,71 @@ 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" @@ -1064,7 +1552,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -1078,6 +1566,15 @@ 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" @@ -1155,6 +1652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1166,6 +1664,41 @@ 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" @@ -1216,6 +1749,41 @@ dependencies = [ "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" @@ -1232,6 +1800,24 @@ 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" @@ -1255,6 +1841,8 @@ dependencies = [ "ipaddress", "itertools", "lettre", + "polars", + "regex", "reqwest", "serde_json", "walkdir", @@ -1267,6 +1855,521 @@ 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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5f7feb5d56b954e691dff22a8b2d78d77433dcc93c35fe21c3777fdc121b697" +dependencies = [ + "getrandom 0.2.16", + "getrandom 0.3.3", + "polars-arrow", + "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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b4fed2343961b3eea3db2cee165540c3e1ad9d5782350cc55a9e76cf440148" +dependencies = [ + "atoi_simd", + "bitflags", + "bytemuck", + "chrono", + "chrono-tz", + "dyn-clone", + "either", + "ethnum", + "getrandom 0.2.16", + "getrandom 0.3.3", + "hashbrown 0.15.3", + "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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138785beda4e4a90a025219f09d0d15a671b2be9091513ede58e05db6ad4413f" +dependencies = [ + "atoi_simd", + "bytemuck", + "chrono", + "either", + "fast-float2", + "hashbrown 0.15.3", + "itoa", + "num-traits", + "polars-arrow", + "polars-error", + "polars-utils", + "rand", + "ryu", + "serde", + "skiplist", + "strength_reduce", + "strum_macros", + "version_check", +] + +[[package]] +name = "polars-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77b1f08ef6dbb032bb1d0d3365464be950df9905f6827a95b24c4ca5518901d" +dependencies = [ + "bitflags", + "boxcar", + "bytemuck", + "chrono", + "chrono-tz", + "comfy-table", + "either", + "hashbrown 0.15.3", + "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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c43d0ea57168be4546c4d8064479ed8b29a9c79c31a0c7c367ee734b9b7158" +dependencies = [ + "boxcar", + "hashbrown 0.15.3", + "polars-arrow", + "polars-error", + "polars-utils", + "serde", + "uuid", +] + +[[package]] +name = "polars-error" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cb5d98f59f8b94673ee391840440ad9f0d2170afced95fc98aa86f895563c0" +dependencies = [ + "object_store", + "parking_lot", + "polars-arrow-format", + "regex", + "signal-hook", + "simdutf8", +] + +[[package]] +name = "polars-expr" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343931b818cf136349135ba11dbc18c27683b52c3477b1ba8ca606cf5ab1965c" +dependencies = [ + "bitflags", + "hashbrown 0.15.3", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-io", + "polars-ops", + "polars-plan", + "polars-row", + "polars-time", + "polars-utils", + "rand", + "rayon", + "recursive", +] + +[[package]] +name = "polars-io" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10388c64b8155122488229a881d1c6f4fdc393bc988e764ab51b182fcb2307e4" +dependencies = [ + "async-trait", + "atoi_simd", + "blake3", + "bytes", + "chrono", + "fast-float2", + "fs4", + "futures", + "glob", + "hashbrown 0.15.3", + "home", + "itoa", + "memchr", + "memmap2", + "num-traits", + "object_store", + "percent-encoding", + "polars-arrow", + "polars-core", + "polars-error", + "polars-parquet", + "polars-schema", + "polars-time", + "polars-utils", + "rayon", + "regex", + "reqwest", + "ryu", + "serde", + "serde_json", + "simdutf8", + "tokio", + "tokio-util", + "url", +] + +[[package]] +name = "polars-lazy" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fb6e2c6c2fa4ea0c660df1c06cf56960c81e7c2683877995bae3d4e3d408147" +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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20a856e98e253587c28d8132a5e7e5a75cb2c44731ca090f1481d45f1d123771" +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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf6062173fdc9ba05775548beb66e76643a148d9aeadc9984ed712bc4babd76" +dependencies = [ + "argminmax", + "base64", + "bytemuck", + "chrono", + "chrono-tz", + "either", + "hashbrown 0.15.3", + "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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1d769180dec070df0dc4b89299b364bf2cfe32b218ecc4ddd8f1a49ae60669" +dependencies = [ + "async-stream", + "base64", + "bytemuck", + "ethnum", + "futures", + "hashbrown 0.15.3", + "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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd3a2e33ae4484fe407ab2d2ba5684f0889d1ccf3ad6b844103c03638e6d0a0" +dependencies = [ + "bitflags", + "bytemuck", + "bytes", + "chrono", + "chrono-tz", + "either", + "hashbrown 0.15.3", + "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", + "strum_macros", + "version_check", +] + +[[package]] +name = "polars-row" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18734f17e0e348724df3ae65f3ee744c681117c04b041cac969dfceb05edabc0" +dependencies = [ + "bitflags", + "bytemuck", + "polars-arrow", + "polars-compute", + "polars-dtype", + "polars-error", + "polars-utils", +] + +[[package]] +name = "polars-schema" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6c1ab13e04d5167661a9854ed1ea0482b2ed9b8a0f1118dabed7cd994a85e3" +dependencies = [ + "indexmap", + "polars-error", + "polars-utils", + "serde", + "version_check", +] + +[[package]] +name = "polars-sql" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e7766da02cc1d464994404d3e88a7a0ccd4933df3627c325480fbd9bbc0a11" +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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f6c6ca1ea01f9dea424d167e4f33f5ec44cd67fbfac9efd40575ed20521f14" +dependencies = [ + "async-channel", + "async-trait", + "atomic-waker", + "bitflags", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "futures", + "memmap2", + "parking_lot", + "percent-encoding", + "pin-project-lite", + "polars-arrow", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-ops", + "polars-parquet", + "polars-plan", + "polars-utils", + "rand", + "rayon", + "recursive", + "slotmap", + "tokio", + "tokio-util", + "version_check", +] + +[[package]] +name = "polars-time" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6a3a6e279a7a984a0b83715660f9e880590c6129ec2104396bfa710bcd76dee" +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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b267021b0e5422d7fbc70fd79e51b9f9a8466c585779373a18b0199e973f29" +dependencies = [ + "bincode", + "bytemuck", + "bytes", + "compact_str", + "either", + "flate2", + "foldhash", + "hashbrown 0.15.3", + "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" @@ -1288,6 +2391,15 @@ 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" @@ -1322,6 +2434,71 @@ dependencies = [ "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" @@ -1344,10 +2521,107 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] -name = "regex" -version = "1.11.1" +name = "rand" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +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", @@ -1357,9 +2631,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1374,9 +2648,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64", "bytes", @@ -1400,6 +2674,9 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -1407,12 +2684,15 @@ dependencies = [ "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", ] @@ -1430,12 +2710,40 @@ dependencies = [ "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" @@ -1456,18 +2764,32 @@ 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", ] @@ -1512,6 +2834,12 @@ 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" @@ -1519,7 +2847,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", - "core-foundation", + "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", @@ -1537,18 +2878,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +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.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1557,14 +2908,26 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +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]] @@ -1590,18 +2953,70 @@ dependencies = [ "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 = "skiplist" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354fd282d3177c2951004953e2fdc4cb342fa159bbee8b829852b6a081c8ea1" +dependencies = [ + "rand", + "thiserror", +] + [[package]] name = "slab" version = "0.4.9" @@ -1611,6 +3026,15 @@ 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" @@ -1637,6 +3061,15 @@ dependencies = [ "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" @@ -1656,6 +3089,45 @@ dependencies = [ "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" @@ -1700,7 +3172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -1727,6 +3199,26 @@ dependencies = [ "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" @@ -1768,6 +3260,21 @@ dependencies = [ "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" @@ -1780,9 +3287,21 @@ dependencies = [ "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" @@ -1812,6 +3331,8 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", + "hashbrown 0.15.3", "pin-project-lite", "tokio", ] @@ -1868,9 +3389,21 @@ 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" @@ -1898,12 +3431,48 @@ 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" @@ -1921,6 +3490,18 @@ 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" @@ -1933,6 +3514,12 @@ 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" @@ -2038,6 +3625,19 @@ 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" @@ -2048,6 +3648,32 @@ dependencies = [ "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" @@ -2057,6 +3683,12 @@ 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" @@ -2065,7 +3697,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.1", "windows-result", "windows-strings 0.4.2", ] @@ -2098,6 +3730,12 @@ 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" @@ -2115,7 +3753,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -2124,7 +3762,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -2133,7 +3771,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -2297,6 +3935,12 @@ 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" @@ -2417,9 +4061,9 @@ dependencies = [ [[package]] name = "zip" -version = "4.3.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aed4ac33e8eb078c89e6cbb1d5c4c7703ec6d299fc3e7c3695af8f8b423468b" +checksum = "2f852905151ac8d4d06fdca66520a661c09730a74c6d4e2b0f27b436b382e532" dependencies = [ "aes", "arbitrary", @@ -2431,7 +4075,7 @@ dependencies = [ "getrandom 0.3.3", "hmac", "indexmap", - "liblzma", + "lzma-rust2", "memchr", "pbkdf2", "ppmd-rust", From 7fb6021e64178e0ccb39ad72fa58ecc330fb98f3 Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Sat, 25 Oct 2025 12:09:28 -0300 Subject: [PATCH 04/13] chore: sanitize data with ai model --- src/groupped_repport.rs | 85 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/src/groupped_repport.rs b/src/groupped_repport.rs index 9408415..d338eac 100644 --- a/src/groupped_repport.rs +++ b/src/groupped_repport.rs @@ -5,10 +5,34 @@ use itertools::Itertools; use polars::prelude::*; use reqwest; use walkdir; +use std::time::Duration; +use std::env; fn main() { - let PROMPT = std::fs::read_to_string("./PROMPT_DATA_SANITIZATION.txt").expect("Failed to read the promp for data sanitization"); + 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 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(); @@ -58,6 +82,65 @@ fn main() { 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 a = 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 | + { + 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 + }) + .take(1) + .map(|file_name_csv| { + let file_contents = std::fs::read_to_string(file_name_csv.path()).expect("Failed to read CSV file"); + + println!("{}", format!("{prompt_data_sanitization} \n{file_contents}")); + 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}"), + "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"); + println!("AI Response: {}", ai_response); + }, + Err(error) => {println!("Error {error}");} + }; + + }) + .for_each(|value| println!("{:?}", value)); + // println!("{:?}", files_inside_folder_on_date); + + return Some(()); + }) + .collect_vec(); + + // println!("{:?}", a); + + // Read CSV files inside folder // Use AI to sanitize the data From 178219c4dfecfd81b89214e3509e741eee77b3e7 Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Wed, 29 Oct 2025 11:54:30 -0300 Subject: [PATCH 05/13] chore: add csv dependencies and bump libraries version --- Cargo.lock | 31 +++++++++++++++++++++++++++---- Cargo.toml | 8 +++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0948f0b..71d0ce1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -581,6 +581,27 @@ dependencies = [ "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" @@ -1381,9 +1402,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lettre" -version = "0.11.18" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56" +checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f" dependencies = [ "base64", "chumsky", @@ -1836,6 +1857,7 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", + "csv", "dotenv", "http", "ipaddress", @@ -1844,6 +1866,7 @@ dependencies = [ "polars", "regex", "reqwest", + "serde", "serde_json", "walkdir", "zip", @@ -4061,9 +4084,9 @@ dependencies = [ [[package]] name = "zip" -version = "5.1.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f852905151ac8d4d06fdca66520a661c09730a74c6d4e2b0f27b436b382e532" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" dependencies = [ "aes", "arbitrary", diff --git a/Cargo.toml b/Cargo.toml index 9d4462d..7200f60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,11 @@ 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 = "5.1.1"} +zip = { version = "6.0.0"} walkdir = { version = "2.5.0"} -lettre = {version = "0.11.18", features = ["builder"]} +lettre = {version = "0.11.19", features = ["builder"]} anyhow = { version = "1.0.100"} -polars = { version = "0.51.0" } +polars = { version = "0.51.0"} +serde = { version = "1.0.228" } +csv = {version = "1.4.0"} regex = { version = "1.12.2" } \ No newline at end of file From 99a4c81a58433da5aed0208e5c7ba360e76e4333 Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Wed, 29 Oct 2025 11:54:44 -0300 Subject: [PATCH 06/13] chore: update data sanitization prompt --- PROMPT_DATA_SANITIZATION.txt | 111 +++++++++++++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 4 deletions(-) diff --git a/PROMPT_DATA_SANITIZATION.txt b/PROMPT_DATA_SANITIZATION.txt index 4b09605..0e5c4e3 100644 --- a/PROMPT_DATA_SANITIZATION.txt +++ b/PROMPT_DATA_SANITIZATION.txt @@ -1,8 +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. -Se não for possível padronizar o arquivo de entrada de acordo com as instruções fornecidas a resposta deve ser vazia. -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, ESCLARECIMENTO, TEMPO DE ESPERA -A coluna pontos deve ter apenas os valores 0 ou 1, se no arquivo de entrada estiver incorreto a resposta deve ser vazia. +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 columa 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 -------------------------------- From 197642727c94019dccb1f6b981ad36ec940d55a7 Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Wed, 29 Oct 2025 11:56:23 -0300 Subject: [PATCH 07/13] chore: add vscode folder to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5e81cba..8969bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ src/.env .env log/ .zip +.vscode/ evaluations # Added by cargo # From ca406b4601d5d8e8872e35a1281a6ae5c42cef9b Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Wed, 29 Oct 2025 12:03:00 -0300 Subject: [PATCH 08/13] chore: parse csv and talk id for evaluation files --- src/groupped_repport.rs | 84 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/src/groupped_repport.rs b/src/groupped_repport.rs index d338eac..9ca95dd 100644 --- a/src/groupped_repport.rs +++ b/src/groupped_repport.rs @@ -2,12 +2,35 @@ use std::fmt::Debug; use chrono::Datelike; use itertools::Itertools; +use polars::prelude::buffer::validate_utf8; use polars::prelude::*; use reqwest; use walkdir; use std::time::Duration; use std::env; +use csv; + +#[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 +} + fn main() { match dotenv::dotenv().ok() { Some(_) => println!("Environment variables loaded from .env file"), @@ -105,14 +128,15 @@ fn main() { }) .take(1) .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"); - println!("{}", format!("{prompt_data_sanitization} \n{file_contents}")); 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() ); @@ -125,13 +149,65 @@ fn main() { let ai_response = response_json["response"] .as_str() .expect("Failed to get AI response as string"); - println!("AI Response: {}", ai_response); + + 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}");} + Err(error) => {println!("Error {error}"); return Err(error);} }; }) - .for_each(|value| println!("{:?}", value)); + .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 correctly_parsed = deserialized_iter.all(|value| { + value.is_ok() && value.unwrap().PONTOS.is_some() + }); + + if !correctly_parsed { 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 talk_id = found_regex_groups_in_filename.get(5).expect("Failed to get the id from regex maches").clone(); + + println!("{:?}", talk_id); + // a.for_each(|val| { println!("{:?}", val)}); + + // println!("{:?}", a); + + // Do validation on CSV parsed + // let records_parsed = reader.records(); + + // if records_parsed.try_len().expect("Failed to obtain lenght") != 9 { return None; } + + // reader.records().for_each(|record| println!("{:?}", record)); + + + return Some(()); + + }) + .for_each(|_value| {}); // println!("{:?}", files_inside_folder_on_date); return Some(()); From 5477f91bb92e8b29587471639a0bbacf3396816e Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Thu, 30 Oct 2025 12:03:23 -0300 Subject: [PATCH 09/13] chore: gather user name and evaluation as a dataframe --- src/groupped_repport.rs | 52 ++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/groupped_repport.rs b/src/groupped_repport.rs index 9ca95dd..f7d70c5 100644 --- a/src/groupped_repport.rs +++ b/src/groupped_repport.rs @@ -5,6 +5,7 @@ use itertools::Itertools; use polars::prelude::buffer::validate_utf8; use polars::prelude::*; use reqwest; +use serde::Deserialize; use walkdir; use std::time::Duration; use std::env; @@ -31,6 +32,14 @@ struct CsvEvaluation { ID_TALK: String } +// impl TryFrom::> for CsvEvaluation { +// type Error = &'static str; + +// fn try_from(value: csv::DeserializeRecordsIter<&[u8], CsvHeader>) -> Result { +// todo!() +// } +// } + fn main() { match dotenv::dotenv().ok() { Some(_) => println!("Environment variables loaded from .env file"), @@ -170,11 +179,20 @@ fn main() { let mut deserialized_iter = reader.deserialize::(); - let correctly_parsed = deserialized_iter.all(|value| { - value.is_ok() && value.unwrap().PONTOS.is_some() - }); + 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 !correctly_parsed { return None; } + if columns.len() != 9 { return None;} // Parse id talk from file_path // filename example is: CC - Erraoander Quintana - 515578 - 20251020515578.csv @@ -189,25 +207,21 @@ fn main() { filename.as_str() ).expect("Failed to do regex capture"); - let talk_id = found_regex_groups_in_filename.get(5).expect("Failed to get the id from regex maches").clone(); + 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"); - println!("{:?}", talk_id); - // a.for_each(|val| { println!("{:?}", val)}); + 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"); - // println!("{:?}", a); - - // Do validation on CSV parsed - // let records_parsed = reader.records(); - - // if records_parsed.try_len().expect("Failed to obtain lenght") != 9 { return None; } - - // reader.records().for_each(|record| println!("{:?}", record)); - - - return Some(()); + println!("{:?}", df); + // Create a dataframe with the evaluation columns plus the talk id + + // 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)); }) - .for_each(|_value| {}); + .for_each(|username| {}); // println!("{:?}", files_inside_folder_on_date); return Some(()); From 14bf25e83a2a7eed089e24c202f6f0317b7c8423 Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Mon, 3 Nov 2025 10:56:00 -0300 Subject: [PATCH 10/13] style: apply cargo fmt --- src/groupped_repport.rs | 394 +++++++++++++++++++++-------------- src/main.rs | 447 ++++++++++++++++++++++++++-------------- 2 files changed, 535 insertions(+), 306 deletions(-) diff --git a/src/groupped_repport.rs b/src/groupped_repport.rs index f7d70c5..2308c99 100644 --- a/src/groupped_repport.rs +++ b/src/groupped_repport.rs @@ -6,9 +6,9 @@ use polars::prelude::buffer::validate_utf8; use polars::prelude::*; use reqwest; use serde::Deserialize; -use walkdir; -use std::time::Duration; use std::env; +use std::time::Duration; +use walkdir; use csv; @@ -29,7 +29,7 @@ struct CsvEvaluation { DISPONIBILIDADE: u8, CONHECIMENTO_TÉCNICO: u8, DIDATISMO: u8, - ID_TALK: String + ID_TALK: String, } // impl TryFrom::> for CsvEvaluation { @@ -52,7 +52,8 @@ fn main() { .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 OLLAMA_AI_MODEL_DATA_SANITIZATION = env::var("OLLAMA_AI_MODEL_DATA_SANITIZATION") + .expect("Missing environment variable OLLAMA_AI_MODEL_DATA_SANITIZATION"); let ip_address = ipaddress::IPAddress::parse(OLLAMA_URL.to_string()); let OLLAMA_SANITIZED_IP = match ip_address { @@ -70,166 +71,253 @@ fn main() { let current_date = chrono::Local::now(); let formatted_date = current_date.format("%Y-%m-%d").to_string(); - 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); + 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"); - 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 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 first_day_of_week_in_folder_name = week.first_day(); + let filename = entry_string_name.to_str().unwrap(); + let matches_find = regex_match_date.find(filename); - 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(); + 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 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 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 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 a = previous_week_folder_names.iter().map(|folder_name| { + let a = 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 | - { - 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"); + .take(1) + .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 - }) - .take(1) - .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() + entry_file_name_as_str.ends_with(".csv") + && !entry_file_name_as_str.contains("response_time.csv") }) - .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());} + .filter_map(|value| { + if value.is_ok() { + return Some(value.unwrap()); + } None }) + // .take(1) + .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"); + + 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"); + + // println!("{:?}", df); + // Create a dataframe with the evaluation columns plus the talk id + + // 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) + }) .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"); - - 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"); - - println!("{:?}", df); - // Create a dataframe with the evaluation columns plus the talk id - - // 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)); - - }) - .for_each(|username| {}); - // println!("{:?}", files_inside_folder_on_date); - - return Some(()); + return Some(groupped_by_user_on_day); }) + .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) + }) + // .collect_vec(); + .collect_vec(); - // println!("{:?}", a); - + println!("{:?}", a); // Read CSV files inside folder @@ -238,14 +326,12 @@ fn main() { // Save into a hashmap, with the user name as key, the date, evaluation // Final file should look like -/* -Header: Att1, att2, att3, ... -categoria1 -categoria2 -categoria3 -... + /* + Header: Att1, att2, att3, ... + categoria1 + categoria2 + categoria3 + ... -*/ - - -} \ No newline at end of file + */ +} diff --git a/src/main.rs b/src/main.rs index 47a68bf..b3fa500 100644 --- a/src/main.rs +++ b/src/main.rs @@ -166,20 +166,34 @@ fn main() -> anyhow::Result<()> { // 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") + 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"); + 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").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); + 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()); @@ -215,41 +229,82 @@ fn main() -> anyhow::Result<()> { 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(); + }) + .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;} + 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 -> Suporte]"); - found.is_some() - }); + 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 -> Suporte]", + ); + found.is_some() + }); match found { - None => {return None;}, + None => { + return None; + } Some(pos) => { let pos_found = pos.0; - if pos_found < MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE {return None;} + 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;} + 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 found = filter_keywords.iter().any(|keyword|{message.to_uppercase().find(&keyword.to_uppercase()).is_some()}); - found - }); + 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 found = filter_keywords.iter().any(|keyword| { + message + .to_uppercase() + .find(&keyword.to_uppercase()) + .is_some() + }); + found + }); - if filter_keywords_found {return None;} + if filter_keywords_found { + return None; + } return Some(json); }); @@ -264,80 +319,122 @@ fn main() -> anyhow::Result<()> { // 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 -> Suporte]"); - let found = message.find("Atendimento entregue da fila de espera para o agente"); - found.is_some() - }); + .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 -> Suporte]"); + let found = + message.find("Atendimento entregue da fila de espera para o agente"); + 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() - }) - .take(1).collect_vec(); - + 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() + }) + .take(1) + .collect_vec(); + 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_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 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 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()); + 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)}) + 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"); + 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) - .take(10) - .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| - { + filtered_chats.clone().skip(0).take(10).for_each(|result| { + let json = result.unwrap(); + let talk_histories = &json["talk_histories"]; + let data = &talk_histories["data"]; - let new_json_filtered = format!( -"{{ + 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: {}, @@ -350,44 +447,71 @@ fn main() -> anyhow::Result<()> { ); // println!("{}", new_json_filtered); new_json_filtered - }).reduce(|acc, e| {format!("{acc}\n{e}")}).expect("Error extracting talk"); + }) + .reduce(|acc, e| format!("{acc}\n{e}")) + .expect("Error extracting talk"); - println!("{prompt}\n {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 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(); + 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); + 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("```", ""); + let csv_response = ai_response.replace("```csv\n", "").replace("```", ""); - // Save the CSV response to a file + // 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}");} - }; - }); + 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}"); @@ -410,7 +534,13 @@ fn main() -> anyhow::Result<()> { 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 { +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 = "13".to_string(); @@ -455,57 +585,70 @@ fn get_piperun_chats_on_date(PIPERUN_API_URL: &String, client: &reqwest::blockin } }; - let mut aggregated_talks = json_response["data"].as_array().expect("Failed to parse messages as array").to_owned(); + 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"); + 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"); - 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()), - ]); + 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(); + 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 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"); + 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 @@ -596,7 +739,7 @@ fn send_email( 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 mailboxes: Mailboxes = to.parse().unwrap(); let to_header: message::header::To = mailboxes.into(); let email = Message::builder() From 47901d4a9fdc24138964fa111cfa8f955c2b902f Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Wed, 5 Nov 2025 08:15:15 -0300 Subject: [PATCH 11/13] chore: update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8969bb4..7f1a0e4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ log/ .zip .vscode/ evaluations +groupped # Added by cargo # # already existing elements were commented out From de9dd7a49a1f0d6d3c14b384cb7d507384350c50 Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Wed, 5 Nov 2025 08:15:49 -0300 Subject: [PATCH 12/13] feat: group repports in a week --- src/groupped_repport.rs | 155 ++++++++++++++++++++++++-------------- src/send_mail_util.rs | 46 +++++++++++ src/zip_directory_util.rs | 70 +++++++++++++++++ 3 files changed, 215 insertions(+), 56 deletions(-) create mode 100644 src/send_mail_util.rs create mode 100644 src/zip_directory_util.rs diff --git a/src/groupped_repport.rs b/src/groupped_repport.rs index 2308c99..54072fa 100644 --- a/src/groupped_repport.rs +++ b/src/groupped_repport.rs @@ -1,17 +1,16 @@ use std::fmt::Debug; -use chrono::Datelike; use itertools::Itertools; -use polars::prelude::buffer::validate_utf8; use polars::prelude::*; use reqwest; -use serde::Deserialize; use std::env; use std::time::Duration; -use walkdir; use csv; +pub mod send_mail_util; +pub mod zip_directory_util; + #[derive(Debug, serde::Deserialize)] struct CsvHeader { CATEGORIA: String, @@ -32,14 +31,6 @@ struct CsvEvaluation { ID_TALK: String, } -// impl TryFrom::> for CsvEvaluation { -// type Error = &'static str; - -// fn try_from(value: csv::DeserializeRecordsIter<&[u8], CsvHeader>) -> Result { -// todo!() -// } -// } - fn main() { match dotenv::dotenv().ok() { Some(_) => println!("Environment variables loaded from .env file"), @@ -54,6 +45,9 @@ fn main() { .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 { @@ -71,6 +65,21 @@ fn main() { 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| { @@ -98,17 +107,6 @@ fn main() { }; }) .filter_map_ok(|(week, directory_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 first_day_of_week_in_folder_name = week.first_day(); if first_day_of_last_week == first_day_of_week_in_folder_name { @@ -132,14 +130,13 @@ fn main() { .expect("Failed to read PROMPT_DATA_SANITIZATION.txt"); let client = reqwest::blocking::Client::new(); - let a = previous_week_folder_names + 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) }) - .take(1) .filter_map_ok(|files_inside_folder_on_date| { let groupped_by_user_on_day = files_inside_folder_on_date .filter_ok(|entry| { @@ -157,7 +154,6 @@ fn main() { } None }) - // .take(1) .map(|file_name_csv| { println!("{:?}", file_name_csv.path()); let file_contents = std::fs::read_to_string(file_name_csv.path()) @@ -271,9 +267,6 @@ fn main() { let df = polars::frame::DataFrame::new(columns) .expect("Failed to concatenate into a dataframe"); - // println!("{:?}", df); - // Create a dataframe with the evaluation columns plus the talk id - // 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)); }) @@ -293,45 +286,95 @@ fn main() { .expect("Failed to concatenate dataframes"); (name, groupped_df) }) - .collect_vec(); + .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; - }) - .into_group_map() - .into_iter() - .map(|(name, eval_dataframe_vec)| { - let groupped_df = eval_dataframe_vec + 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"); - (name, groupped_df) + (key.clone(), dfs) }) - // .collect_vec(); - - .collect_vec(); + .collect_vec(); + return Some(result); + }); - println!("{:?}", a); + // Setup groupped folder + if !std::fs::exists(format!("./groupped/")).unwrap() { + std::fs::create_dir(format!("./groupped")).expect("Failed to create directory") + } - // Read CSV files inside folder + // 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") + } - // Use AI to sanitize the data + 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}" + )) + .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 => {} + } - // Save into a hashmap, with the user name as key, the date, evaluation + 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" + )), + ); - // Final file should look like - /* - Header: Att1, att2, att3, ... - categoria1 - categoria2 - categoria3 - ... - - */ + let recipients = "Wilson da Conceição Oliveira , Isadora G. Moura de Moura "; + send_mail_util::send_mail_util::send_email( + &format!( + "Relatório agrupado dos atendimentos 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/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..d5fdc51 --- /dev/null +++ b/src/zip_directory_util.rs @@ -0,0 +1,70 @@ +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); +} + +} \ No newline at end of file From 6037643a2864186c43504a0cb552b52dfed20374 Mon Sep 17 00:00:00 2001 From: Jelson Rodrigues Date: Wed, 5 Nov 2025 08:16:27 -0300 Subject: [PATCH 13/13] chore: update gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7f1a0e4..4e46adf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ src/.env .env log/ .zip -.vscode/ +.vscode evaluations groupped # Added by cargo