diff --git a/.env.noc-cli b/.env.noc-cli new file mode 100644 index 0000000..dbf5b24 --- /dev/null +++ b/.env.noc-cli @@ -0,0 +1,22 @@ +OLLAMA_URL=2804:11d4:1:1002::14:18 +OLLAMA_PORT=11434 +OLLAMA_AI_MODEL=gpt-oss:20b +PIPERUN_API_URL=novanet.cxm.pipe.run +PIPERUN_CLIENT_ID=14 +PIPERUN_CLIENT_SECRET=5MzZLKPjAexuANkl7g5dzyzm3sKqO8r159iSoF4x +PIPERUN_BOT_USERNAME=bot.piperun@nova.net.br +PIPERUN_BOT_PASSWORD=s?KKN=twKEjTcTxoMv52=9fF5EwFT +MINIMUM_NUMBER_OF_MESSAGES_TO_EVALUATE=10 +MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE=10 +BOT_EMAIL=bot.piperun@nova.net.br +BOT_EMAIL_PASSWORD=diQAVg,gK7rZ:5XRsqJ4zFay:fkAaH.4 +OLLAMA_AI_MODEL_DATA_SANITIZATION=phi4:latest +FILA_ID=19 +PROMPT_X=PROMPT_NOC-CLI.txt +FILTER_X=FILTER_NOC-CLI.txt +PASTA=evaluations_noc-cli +NOME_FILA="NOC - Clientes" +INICIAL_AGENTE="NOC -" +EMAIL_1=nicolas.borges@nova.net.br +EMAIL_2=wilson.oliveira@nova.net.br + diff --git a/.env.noc-corp b/.env.noc-corp new file mode 100644 index 0000000..41b56b0 --- /dev/null +++ b/.env.noc-corp @@ -0,0 +1,21 @@ +OLLAMA_URL=2804:11d4:1:1002::14:18 +OLLAMA_PORT=11434 +OLLAMA_AI_MODEL=gpt-oss:20b +PIPERUN_API_URL=novanet.cxm.pipe.run +PIPERUN_CLIENT_ID=14 +PIPERUN_CLIENT_SECRET=5MzZLKPjAexuANkl7g5dzyzm3sKqO8r159iSoF4x +PIPERUN_BOT_USERNAME=bot.piperun@nova.net.br +PIPERUN_BOT_PASSWORD=s?KKN=twKEjTcTxoMv52=9fF5EwFT +MINIMUM_NUMBER_OF_MESSAGES_TO_EVALUATE=10 +MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE=10 +BOT_EMAIL=bot.piperun@nova.net.br +BOT_EMAIL_PASSWORD=diQAVg,gK7rZ:5XRsqJ4zFay:fkAaH.4 +OLLAMA_AI_MODEL_DATA_SANITIZATION=phi4:latest +FILA_ID=23 +PROMPT_X=PROMPT_NOC-CORP.txt +FILTER_X=FILTER_NOC-CORP.txt +PASTA=evaluations_noc-corp +NOME_FILA="NOC - Corporativo" +INICIAL_AGENTE="NOC -" +EMAIL_1=nicolas.borges@nova.net.br +EMAIL_2=wilson.oliveira@nova.net.br diff --git a/src/main.rs b/src/main.rs index 10c5c5e..e5a0a5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,121 +14,6 @@ pub mod send_mail_util; pub mod zip_directory_util; fn main() -> anyhow::Result<()> { - // Get the current day in the format YYYY-MM-DD - let current_date = chrono::Local::now(); - let formatted_date = current_date.format("%Y-%m-%d").to_string(); - - println!("Current date: {}", formatted_date); - - // Get the day before the current date - let day_before = current_date - .checked_sub_signed(chrono::Duration::days(1)) - .expect("Failed to get the day before"); - let formatted_day_before = day_before.format("%Y-%m-%d").to_string(); - - println!("Day before: {}", formatted_day_before); - - let day_before_at_midnight = day_before - .with_hour(0) - .unwrap() - .with_minute(0) - .unwrap() - .with_second(0) - .unwrap(); - let formatted_day_before_at_midnight = - day_before_at_midnight.format("%Y-%m-%d %H:%M").to_string(); - - let day_before_at_23_59_59 = day_before - .with_hour(23) - .unwrap() - .with_minute(59) - .unwrap() - .with_second(59) - .unwrap(); - let formatted_day_before_at_23_59_59 = - day_before_at_23_59_59.format("%Y-%m-%d %H:%M").to_string(); - - println!( - "Day before at midnight: {}, Day before at 23:59:59: {}", - formatted_day_before_at_midnight, formatted_day_before_at_23_59_59 - ); - - let formatted_day_before = day_before_at_midnight.format("%Y-%m-%d").to_string(); - - // Create a folder named with the day_before - if !std::fs::exists(format!("./evaluations/evaluations_noc-cli/{formatted_day_before}")).unwrap() { - std::fs::create_dir(format!("./evaluations/evaluations_noc-cli/{formatted_day_before}")) - .expect("Failed to create directory") - } - - // Create the response time folder - if !std::fs::exists(format!( - "./evaluations/evaluations_noc-cli/{formatted_day_before}/response_time.csv" - )) - .unwrap() - { - let mut response_time_file = std::fs::File::create_new(format!( - "./evaluations/evaluations_noc-cli/{formatted_day_before}/response_time.csv" - )) - .expect("Failed to response_time.csv"); - } - -/* - // --- NOVO: processar argumento de linha de comando para data específica --- - use std::env; - use chrono::NaiveDate; - - let args: Vec = env::args().collect(); - //Primeiro argumento: data. - let target_day = if args.len() > 1 { - // Se um argumento foi passado, interpretar como YYYY-MM-DD - let naive_date = NaiveDate::parse_from_str(&args[1], "%Y-%m-%d") - .expect("Formato de data inválido. Use YYYY-MM-DD"); - // Converter para DateTime com hora 00:00:00 do fuso local - naive_date.and_hms_opt(0, 0, 0).unwrap() - } else { - // Comportamento padrão: dia anterior ao atual - (chrono::Local::now() - chrono::Duration::days(1)).naive_local() - }; - - println!("Processando o dia: {}", target_day.format("%Y-%m-%d")); - - // Definir início e fim do dia (para consultas na API) - let day_start = target_day; // já 00:00 - let day_end = target_day - .with_hour(23).unwrap() - .with_minute(59).unwrap() - .with_second(59).unwrap(); - - let formatted_day = target_day.format("%Y-%m-%d").to_string(); - let formatted_day_start = day_start.format("%Y-%m-%d %H:%M").to_string(); - let formatted_day_end = day_end.format("%Y-%m-%d %H:%M").to_string(); - - println!("Início do dia: {}", formatted_day_start); - println!("Fim do dia: {}", formatted_day_end); - - // Criar pasta com o nome do dia processado - if !std::fs::exists(format!("./evaluations/evaluations_noc-cli/{formatted_day}")).unwrap() { - std::fs::create_dir(format!("./evaluations/evaluations_noc-cli/{formatted_day}")) - .expect("Failed to create directory"); - } - - // Criar arquivo response_time.csv se não existir - if !std::fs::exists(format!("./evaluations/evaluations_noc-cli/{formatted_day}/response_time.csv")).unwrap() { - let _ = std::fs::File::create_new(format!( - "./evaluations/evaluations_noc-cli/{formatted_day}/response_time.csv" - )) - .expect("Failed to create response_time.csv"); - } - - // Segundo argumento: arquivo .env - if args.len() > 2 { - let env_path = &args[2]; - dotenv::from_path(env_path).expect("Falha ao carregar .env"); - } else { - dotenv::dotenv().ok(); - } -*/ // coleta os argumentos da linha de comando. let args: Vec = env::args().collect(); //Primeiro argumento: arquivo .env @@ -178,8 +63,16 @@ fn main() -> anyhow::Result<()> { .parse::() .unwrap_or(12); let BOT_EMAIL = env::var("BOT_EMAIL").expect("BOT_EMAIL has not been set!"); - let BOT_EMAIL_PASSWORD = - env::var("BOT_EMAIL_PASSWORD").expect("BOT_EMAIL_PASSWORD has not been set!"); + let BOT_EMAIL_PASSWORD = env::var("BOT_EMAIL_PASSWORD").expect("BOT_EMAIL_PASSWORD has not been set!"); + let FILA_ID = env::var("FILA_ID").expect("FILA_ID has not been set!"); + let PROMPT_X = env::var("PROMPT_X").expect("PROMPT_X has not been set!"); + let FILTER_X = env::var("FILTER_X").expect("FILTER_X has not been set!"); + let PASTA = env::var("PASTA").expect("PASTA has not been set!"); + let NOME_FILA = env::var("NOME_FILA").expect("NOME_FILA has not been set!"); + let INICIAL_AGENTE = env::var("INICIAL_AGENTE").expect("INICIAL_AGENTE has not been set!"); + let EMAIL_1 = env::var("EMAIL_1").expect("EMAIL_1 has not been set!"); + let EMAIL_2 = env::var("EMAIL_2").expect("EMAIL_2 has not been set!"); +// let EMAIL_3 = env::var("EMAIL_3").expect("EMAIL_3 has not been set!"); // Print the configuration println!("OLLAMA_URL: {}", OLLAMA_URL); @@ -190,6 +83,127 @@ fn main() -> anyhow::Result<()> { println!("PIPERUN_CLIENT_SECRET: {}", PIPERUN_CLIENT_SECRET); println!("PIPERUN_BOT_USERNAME: {}", PIPERUN_BOT_USERNAME); println!("PIPERUN_BOT_PASSWORD: {}", PIPERUN_BOT_PASSWORD); + println!("FILA_ID: {}", FILA_ID); + println!("NOME_FILA: {}", NOME_FILA); + println!("INICIAL_AGENTE: {}", INICIAL_AGENTE); + println!("PASTA: {}", PASTA); + println!("PROMPT_X: {}", PROMPT_X); + println!("FILTER_X: {}", FILTER_X); + + // Get the current day in the format YYYY-MM-DD + let current_date = chrono::Local::now(); + let formatted_date = current_date.format("%Y-%m-%d").to_string(); + + println!("Current date: {}", formatted_date); + + // Get the day before the current date + let day_before = current_date + .checked_sub_signed(chrono::Duration::days(1)) + .expect("Failed to get the day before"); + let formatted_day_before = day_before.format("%Y-%m-%d").to_string(); + + println!("Day before: {}", formatted_day_before); + + let day_before_at_midnight = day_before + .with_hour(0) + .unwrap() + .with_minute(0) + .unwrap() + .with_second(0) + .unwrap(); + let formatted_day_before_at_midnight = + day_before_at_midnight.format("%Y-%m-%d %H:%M").to_string(); + + let day_before_at_23_59_59 = day_before + .with_hour(23) + .unwrap() + .with_minute(59) + .unwrap() + .with_second(59) + .unwrap(); + let formatted_day_before_at_23_59_59 = + day_before_at_23_59_59.format("%Y-%m-%d %H:%M").to_string(); + + println!( + "Day before at midnight: {}, Day before at 23:59:59: {}", + formatted_day_before_at_midnight, formatted_day_before_at_23_59_59 + ); + + let formatted_day_before = day_before_at_midnight.format("%Y-%m-%d").to_string(); + // --- Criação de diretórios usando a variável PASTA --- + let pasta_base = format!("./evaluations/{}", PASTA); + let pasta_dia = format!("{}/{}", pasta_base, formatted_day_before); + + if !std::fs::exists(&pasta_base).unwrap_or(false) { + std::fs::create_dir_all(&pasta_base) + .expect("Falha ao criar diretório base"); + } + if !std::fs::exists(&pasta_dia).unwrap_or(false) { + std::fs::create_dir_all(&pasta_dia) + .expect("Falha ao criar diretório do dia"); + } + + let response_time_path = format!("{}/response_time.csv", pasta_dia); + if !std::fs::exists(&response_time_path).unwrap_or(false) { + let _ = std::fs::File::create_new(&response_time_path) + .expect("Falha ao criar response_time.csv"); + } +/* + // --- NOVO: processar argumento de linha de comando para data específica --- + use std::env; + use chrono::NaiveDate; + + let args: Vec = env::args().collect(); + //Primeiro argumento: data. + let target_day = if args.len() > 1 { + // Se um argumento foi passado, interpretar como YYYY-MM-DD + let naive_date = NaiveDate::parse_from_str(&args[1], "%Y-%m-%d") + .expect("Formato de data inválido. Use YYYY-MM-DD"); + // Converter para DateTime com hora 00:00:00 do fuso local + naive_date.and_hms_opt(0, 0, 0).unwrap() + } else { + // Comportamento padrão: dia anterior ao atual + (chrono::Local::now() - chrono::Duration::days(1)).naive_local() + }; + + println!("Processando o dia: {}", target_day.format("%Y-%m-%d")); + + // Definir início e fim do dia (para consultas na API) + let day_start = target_day; // já 00:00 + let day_end = target_day + .with_hour(23).unwrap() + .with_minute(59).unwrap() + .with_second(59).unwrap(); + + let formatted_day = target_day.format("%Y-%m-%d").to_string(); + let formatted_day_start = day_start.format("%Y-%m-%d %H:%M").to_string(); + let formatted_day_end = day_end.format("%Y-%m-%d %H:%M").to_string(); + + println!("Início do dia: {}", formatted_day_start); + println!("Fim do dia: {}", formatted_day_end); + + // Criar pasta com o nome do dia processado + if !std::fs::exists(format!("./evaluations/evaluations_noc-cli/{formatted_day}")).unwrap() { + std::fs::create_dir(format!("./evaluations/evaluations_noc-cli/{formatted_day}")) + .expect("Failed to create directory"); + } + + // Criar arquivo response_time.csv se não existir + if !std::fs::exists(format!("./evaluations/evaluations_noc-cli/{formatted_day}/response_time.csv")).unwrap() { + let _ = std::fs::File::create_new(format!( + "./evaluations/evaluations_noc-cli/{formatted_day}/response_time.csv" + )) + .expect("Failed to create response_time.csv"); + } + + // Segundo argumento: arquivo .env + if args.len() > 2 { + let env_path = &args[2]; + dotenv::from_path(env_path).expect("Falha ao carregar .env"); + } else { + dotenv::dotenv().ok(); + } +*/ let ip_address = ipaddress::IPAddress::parse(OLLAMA_URL.to_string()); let OLLAMA_SANITIZED_IP = match ip_address { @@ -257,8 +271,10 @@ fn main() -> anyhow::Result<()> { }; // Read system prompt - let prompt = std::fs::read_to_string("./prompts/PROMPT_NOC-CLI.txt").unwrap(); - let filter_file_contents = std::fs::read_to_string("./filters/FILTER_NOC-CLI.txt").unwrap_or(String::new()); + let prompt_path = format!("./prompts/{}",PROMPT_X); + let prompt = std::fs::read_to_string(&prompt_path).unwrap_or_else(|_| panic!("Falha ao ler {}.", &prompt_path)); + let filter_path = format!("./filters/{}",FILTER_X); + let filter_file_contents = std::fs::read_to_string(&filter_path).unwrap_or_default(); let filter_keywords = filter_file_contents .split("\n") .filter(|keyword| !keyword.is_empty()) @@ -272,6 +288,7 @@ fn main() -> anyhow::Result<()> { //formatted_day_start, formatted_day_before_at_23_59_59, //formatted_day_end, + &FILA_ID, ); println!("Number of consolidated talks: {}", talks_array.len()); @@ -339,10 +356,8 @@ fn main() -> anyhow::Result<()> { let template_id = message_object["is_template"] .as_bool() .unwrap_or(true); - let found = message.find( - "Atendimento transferido para a fila [NovaNet -> Atendimento -> NOC - Clientes]", - ); - + let mt_fila = format!("Atendimento transferido para a fila [NovaNet -> Atendimento -> {}]", NOME_FILA); + let found = message.find(&mt_fila ); found.is_some() || template_id }); @@ -408,6 +423,7 @@ fn main() -> anyhow::Result<()> { let response_time = filtered_chats .clone() .map(|messages| { + let mt_agente = format!("Atendimento entregue da fila de espera para o agente [{}",INICIAL_AGENTE); let json = messages.unwrap(); let talk_histories = &json["talk_histories"]; let histories = talk_histories.as_array() @@ -418,8 +434,7 @@ fn main() -> anyhow::Result<()> { let is_template = msg["is_template"].as_bool().unwrap_or(false); let user = msg["user"]["name"].as_str().unwrap_or(""); let message = msg["message"].as_str().unwrap_or(""); - let is_transfer = user == "PipeBot" - && message.contains("Atendimento entregue da fila de espera para o agente [NOC"); + let is_transfer = user == "PipeBot" && message.contains(&mt_agente); is_template || is_transfer }); @@ -432,8 +447,7 @@ fn main() -> anyhow::Result<()> { let is_transfer_case = { let user = trigger_msg["user"]["name"].as_str().unwrap_or(""); let message = trigger_msg["message"].as_str().unwrap_or(""); - user == "PipeBot" - && message.contains("Atendimento entregue da fila de espera para o agente [NOC") + user == "PipeBot" && message.contains(&mt_agente) }; let (ref_pos, ref_msg) = if is_transfer_case { @@ -455,8 +469,11 @@ fn main() -> anyhow::Result<()> { let agent_msg = (0..ref_pos).rev() .filter_map(|i| { let m = &histories[i]; - if m["type"].as_str() == Some("out") - && m["user"]["name"].as_str().map_or(false, |name| name.starts_with("NOC -")) + if m["type"].as_str() == Some("out") && + m["user"]["name"] + .as_str() + .map(|name| name.starts_with(&INICIAL_AGENTE)) + .unwrap_or(false) { Some(m) } else { @@ -503,17 +520,15 @@ fn main() -> anyhow::Result<()> { // Open file and write to it let header = "NOME;ID_TALK;TEMPO DE RESPOSTA;TRANFERENCIA PELO BOT;PRIMEIRA RESPOSTA DO AGENTE"; + let pasta_dia = format!("./evaluations/{}/{}", PASTA, formatted_day_before); + //let pasta_dia = format!("./evaluations/{}/{}", PASTA, formatted_day); let mut response_time_file = std::fs::OpenOptions::new() - .write(true) - .open(format!( - "./evaluations/evaluations_noc-cli/{formatted_day_before}/response_time.csv" - //"./evaluations/evaluations_noc-cli{formatted_day}/response_time.csv" - )) - .expect("Failed to open response time file for write"); + .write(true) + .open(&response_time_path) + .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"); - + .write_all(format!("{header}\n{response_time}").as_bytes()) + .expect("Failed to write header to file"); filtered_chats.clone().skip(0).for_each(|result| { let json = result.unwrap(); let talk_histories = &json["talk_histories"]; @@ -524,6 +539,8 @@ fn main() -> anyhow::Result<()> { .expect("Wrong message type received from talk histories") .iter() .rev() + .filter(|msg| msg["type"].as_str() == Some("out")) +/* //filtro para escrever somente as mensagens out no .txt .filter(|(message_object)| { let message_type = message_object["type"] @@ -532,6 +549,7 @@ fn main() -> anyhow::Result<()> { message_type == "out".to_string() }) //fim do filtro +*/ .map(|message_object| { let new_json_filtered = format!( "{{ @@ -588,51 +606,40 @@ fn main() -> anyhow::Result<()> { .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/evaluations_noc-cli/{}/{} - {} - {}.csv", - formatted_day_before, user_name, talk_id, tracking_number - //formatted_day, user_name, talk_id, tracking_number - ), - csv_response, - ) - .expect("Unable to write file"); - std::fs::write( - format!( - "./evaluations/evaluations_noc-cli/{}/{} - {} - {} - prompt.txt", - formatted_day_before, user_name, talk_id, tracking_number - //formatted_day, user_name, talk_id, tracking_number - ), - format!("{prompt} \n{talk}"), - ) - .expect("Unable to write file"); + std::fs::write( + format!("{}/{} - {} - {}.csv", pasta_dia, user_name, talk_id, tracking_number), + csv_response, + ) + .expect("Unable to write file"); + std::fs::write( + format!("{}/{} - {} - {} - prompt.txt", pasta_dia, user_name, talk_id, tracking_number), + format!("{prompt} \n{talk}"), + ) + .expect("Unable to write file"); } Err(error) => { println!("Error {error}"); } }; }); - + let nome_fila_email = format!("{}", NOME_FILA); // Compress folder into zip - let source_dir_str = format!("./evaluations/evaluations_noc-cli/{formatted_day_before}"); - //let source_dir_str = format!("./evaluations/evaluations_noc-cli/{formatted_day}"); - let output_zip_file_str = format!("./evaluations/evaluations_noc-cli/{formatted_day_before}.zip"); - //let output_zip_file_str = format!("./evaluations/evaluations_noc-cli/{formatted_day}.zip"); - let source_dir = std::path::Path::new(source_dir_str.as_str()); - let output_zip_file = std::path::Path::new(output_zip_file_str.as_str()); - zip_directory_util::zip_directory_util::zip_source_dir_to_dst_file(source_dir, output_zip_file); - + let source_dir = std::path::Path::new(&pasta_dia); + let output_zip_file_str = format!("{}.zip", pasta_dia); + let output_zip_file = std::path::Path::new(&output_zip_file_str); + zip_directory_util::zip_directory_util::zip_source_dir_to_dst_file(source_dir, output_zip_file); // Send folder to email - let recipients = "nicolas.borges@nova.net.br, wilson@nova.net.br"; - println!("Trying to send email... Recipients {recipients}"); + let recipients = format!("{}, {}", EMAIL_1, EMAIL_2); + //let recipients = "nicolas@nova.net.br, wilson@nova.net.br"; + println!("Trying to send email... Recipients {}", recipients); send_mail_util::send_mail_util::send_email( - &format!("Avaliacao atendimentos da fila NOC - Clientes do dia {formatted_day_before}"), - //&format!("Avaliacao atendimentos da fila NOC - Clientes do dia {formatted_day}"), + &format!("Avaliacao atendimentos da fila {} do dia {}", nome_fila_email, formatted_day_before), + //&format!("Avaliacao atendimentos da fila {} do dia {}", nome_fila_email, formatted_day), &BOT_EMAIL, &BOT_EMAIL_PASSWORD, - recipients, + &recipients, &output_zip_file_str, ); @@ -644,14 +651,11 @@ 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_start: String, - formatted_day_before_at_23_59_59: String, - //formatted_day_end: String, + formatted_day_start: String, + formatted_day_end: String, + fila_id: &String, ) -> Vec { let start_of_talk_code: String = "talk_start".to_string(); - let support_queue_id: String = "19".to_string(); - // API V2 let report_type = "consolidated".to_string(); let page = "1".to_string(); @@ -666,12 +670,10 @@ fn get_piperun_chats_on_date( ("page", page.clone()), ("perPage", per_page.clone()), ("report_type", report_type.clone()), - ("start_date", formatted_day_before_at_midnight.clone()), - //("start_date", formatted_day_start.clone()), - ("end_date", formatted_day_before_at_23_59_59.clone()), - //("end_date", formatted_day_end.clone()), + ("start_date", formatted_day_start.clone()), + ("end_date", formatted_day_end.clone()), ("date_range_type", start_of_talk_code.clone()), - ("queue_id[]", support_queue_id.clone()), + ("queue_id[]", fila_id.to_string()), ]); println!("Sending request for consolidated talks... {talks_request:?}"); @@ -724,12 +726,10 @@ fn get_piperun_chats_on_date( ("page", page_to_request.to_string()), ("perPage", per_page.clone()), ("report_type", report_type.clone()), - ("start_date", formatted_day_before_at_midnight.clone()), - //("start_date", formatted_day_start.clone()), - ("end_date", formatted_day_before_at_23_59_59.clone()), - //("end_date", formatted_day_end.clone()), + ("start_date", formatted_day_start.clone()), + ("end_date", formatted_day_end.clone()), ("date_range_type", start_of_talk_code.clone()), - ("queue_id[]", support_queue_id.clone()), + ("queue_id[]", fila_id.to_string()), ]); println!("Sending request for consolidated talks... {talks_request:?}");