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()