Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c5b3a70c12 | |||
| 5935e844a4 | |||
| 8ac0de337d | |||
| 383348ee02 | |||
| ea3634a7c2 | |||
| 99fef6bad4 | |||
| f213e1c4e9 | |||
| 2f11fb1b1d | |||
| b95e3f87d1 | |||
| 2564f38fa9 | |||
| cdfc9fc5e7 | |||
| 554c4b25bd | |||
| cd205cf266 | |||
| 7753beacaf |
13
.env
Normal file
13
.env
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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?K<W>KN=twKEjTcTxoMv52=9fF5EwFT
|
||||||
|
MINIMUM_NUMBER_OF_MESSAGES_TO_EVALUATE=15
|
||||||
|
MINIMUM_NUMBER_OF_MESSAGES_WITH_AGENT_TO_EVALUATE=18
|
||||||
|
BOT_EMAIL=bot.piperun@nova.net.br
|
||||||
|
BOT_EMAIL_PASSWORD=diQAVg,gK7rZ:5XRsqJ4zFay:fkAaH.4
|
||||||
|
OLLAMA_AI_MODEL_DATA_SANITIZATION=phi4:latest
|
||||||
11
FILTER.txt
11
FILTER.txt
@@ -16,3 +16,14 @@ link de ativação
|
|||||||
paramount
|
paramount
|
||||||
https://play.watch.tv.br/ative
|
https://play.watch.tv.br/ative
|
||||||
watch
|
watch
|
||||||
|
prosseguimento por ligação
|
||||||
|
seguimento por ligação
|
||||||
|
Agendamento [privado] efetuado por
|
||||||
|
Atendimento transferido para o agente [ATEND - Nicollas Timm] por [ATEND -
|
||||||
|
Atendimento transferido para o agente [ATEND - Ravel Borges] por [ATEND -
|
||||||
|
Atendimento transferido para o agente [ATEND - Renata Ramson] por [ATEND -
|
||||||
|
Atendimento transferido para o agente [ATEND - Isadora Moura] por [ATEND -
|
||||||
|
Atendimento transferido para o agente [ATEND - Erraoander Quintana] por [ATEND -
|
||||||
|
Atendimento transferido para o agente [ATEND - Estevan Macedo] por [ATEND -
|
||||||
|
Atendimento transferido para o agente [ATEND - Rafael Moura] por [ATEND -
|
||||||
|
Atendimento transferido para a fila de espera pelo agente [ATEND -
|
||||||
|
|||||||
59
PROMPT.txt
59
PROMPT.txt
@@ -8,52 +8,25 @@ SEGUINDO OS CRITÉRIOS QUE VÃO ESTAR ABAIXO, AVALIE ATENDIMENTOS DE SUPORTE PRE
|
|||||||
|
|
||||||
04 (PROTOCOLO) - O AGENTE DEVE INFORMAR O PROTOCOLO DE ATENDIMENTO DURANTE A CONVERSA.
|
04 (PROTOCOLO) - O AGENTE DEVE INFORMAR O PROTOCOLO DE ATENDIMENTO DURANTE A CONVERSA.
|
||||||
|
|
||||||
05 (USO DO PORTUGUÊS) - O AGENTE DEVE UTILIZAR CORRETAMENTE O PORTUGUÊS..
|
05 (USO DO PORTUGUÊS) – AVALIE EXCLUSIVAMENTE SE O AGENTE ESTÁ PONTUANDO CORRETAMENTE, ACENTUANDO CORRETAMENTE E INICIANDO AS FRASES COM LETRA MAIÚSCULA.
|
||||||
NÃO ESTÁ ERRADO SE O AGENTE UTILIZAR 'TU', 'TEU', 'TUA'. SOMOS UMA EMPRESA REGIONAL.
|
IGNORE COMPLETAMENTE QUALQUER ERRO NAS MENSAGENS type: "in".
|
||||||
|
É PERMITIDO O USO DE LINGUAGEM REGIONAL COMO “TU”, “TEU”, “TUA”, POIS ISSO FAZ PARTE DA IDENTIDADE DA EMPRESA.
|
||||||
|
|
||||||
06 (PACIÊNCIA E EDUCAÇÃO) - O AGENTE DEVE SER PACIENTE E EDUCADO, UTILIZANDO O USO DE AGRADECIMENTOS, SAUDAÇÕES, PEDIDOS DE DESCULPAS E LINGUAGEM RESPEITOSA, COMO POR EXEMPLO "COMO POSSO TE AJUDAR?", "PERFEITO", "POR GENTILEZA", "OBRIGADO PELAS INFORMAÇÕES", "VOU VERIFICAR, AGUARDE UM MOMENTO POR GENTILEZA", "DESCULPE, MAS NÃO TE COMPREENDI", "PODERIA EXPLICAR DE NOVO?".
|
06 (PACIÊNCIA E EDUCAÇÃO) - O AGENTE DEVE SER PACIENTE E EDUCADO, UTILIZANDO O USO DE AGRADECIMENTOS, SAUDAÇÕES, PEDIDOS DE DESCULPAS E LINGUAGEM RESPEITOSA.
|
||||||
|
COMO POR EXEMPLO "COMO POSSO TE AJUDAR?", "PERFEITO", "POR GENTILEZA", "OBRIGADO PELAS INFORMAÇÕES", "VOU VERIFICAR, AGUARDE UM MOMENTO POR GENTILEZA", "DESCULPE, MAS NÃO TE COMPREENDI", "PODERIA EXPLICAR DE NOVO?".
|
||||||
|
|
||||||
07 (DISPONIBILIDADE) - O AGENTE DEVE SE COLOCAR À DISPOSIÇÃO DO CLIENTE, DEIXANDO CLARO QUE A EMPRESA ESTÁ SEMPRE DISPONÍVEL PARA AJUDAR.
|
07 (DISPONIBILIDADE) - O AGENTE DEVE SE COLOCAR À DISPOSIÇÃO DO CLIENTE, DEIXANDO CLARO QUE A EMPRESA ESTÁ SEMPRE DISPONÍVEL PARA AJUDAR.
|
||||||
|
COMO POR EXEMPLO "CASO TENHA ALGUMA DÚVIDA, NOS CONTATE. A EQUIPE DA NOVANET ESTÁ SEMPRE AQUI PARA TE AJUDAR."
|
||||||
|
|
||||||
08 (CONHECIMENTO TÉCNICO) - O AGENTE DEVE INFORMAR DE FORMA CLARA SE IDENTIFICOU ALGUM PROBLEMA NA CONEXÃO OU SE ESTÁ TUDO NORMAL. COMO POR EXEMPLO:
|
08 (CONHECIMENTO TÉCNICO) - O AGENTE DEVE INFORMAR DE FORMA CLARA SE IDENTIFICOU OU NÃO ALGUM PROBLEMA NA CONEXÃO OU NOS EQUIPAMENTOS DO CLIENTE.
|
||||||
"VERIFICANDO ATRAVÉS DOS EQUIPAMENTOS QUE ESTÃO NO LOCAL, NÃO ENCONTREI NENHUM PROBLEMA QUE PUDESSE OCASIONAR O MAU FUNCIONAMENTO DA TUA CONEXÃO."
|
|
||||||
"ATÉ A TUA CONEXÃO ESTA TUDO CERTO, OS EQUIPAMENTOS ESTÃO PADRONIZADOS E O VELOCIDADE ESTÁ SENDO ENTREGUE."
|
|
||||||
"DE FORMA REMOTA, IDENTIFIQUEI QUE O EQUIPAMENTO DE FIBRA, O MENOR SEM ANTENAS, NÃO ESTÁ RECEBENDO ENERGIA."
|
|
||||||
"DE FORMA REMOTA EU IDENTIFIQUEI QUE O EQUIPAMENTO DA FIBRA NÃO ESTÁ RECEBENDO ENERGIA"
|
|
||||||
"ATÉ TUA ANTENA ESTA TUDO CERTO, O SINAL QUE CHEGA ATÉ ELA É BOM, ESTÁ DENTRO DO PADRÃO"
|
|
||||||
|
|
||||||
09 (DIDATISMO) – O AGENTE DEVE SER DIDÁTICO, EXPLICANDO O MOTIVO DE CADA SOLICITAÇÃO OU VERIFICAÇÃO.
|
09 (DIDATISMO) – O AGENTE DEVE SER DIDÁTICO DURANTE O ATENDIMENTO, EXPLICANDO DE FORMA CLARA CADA AÇÃO REALIZADA OU SOLICITADA AO CLIENTE.
|
||||||
A EXPLICAÇÃO PODE VIR ANTES OU DEPOIS DA SOLICITAÇÃO.
|
O AGENTE É CONSIDERADO DIDÁTICO QUANDO PEDE PARA O CLIENTE REALIZAR ALGUM PROCEDIMENTO E EXPLICA O MOTIVO DA SOLICITAÇÃO.
|
||||||
NÃO É NECESSÁRIO USAR AS MESMAS PALAVRAS DOS EXEMPLOS, BASTA DEIXAR CLARO O PORQUÊ.
|
QUANDO PEDE PARA O CLIENTE FAZER ALGUMA VERIFICAÇÃO E EXPLICA O MOTIVO.
|
||||||
✅ EXEMPLOS CORRETOS:
|
QUANDO ESCLARECE A SITUAÇÃO INFORMANDO ONDE ESTAVA O PROBLEMA E COMO ELE FOI OU SERÁ RESOLVIDO, OU QUANDO FAZ ALGUMA RECOMENDAÇÃO TÉCNICA PARA O CLIENTE, EXPLICANDO O MOTIVO DESSA RECOMENDAÇÃO.
|
||||||
"ACESSEI A ANTENA E VI QUE ESTAVA UM POUCO LENTO, REINICIEI A MESMA PARA DESCARTAR UM TRAVAMENTO. PODERIA VERIFICAR SE ESTÁ FUNCIONANDO AGORA?"
|
SOLICITAÇÃO OU CONFIRMAÇÃO DE E-MAIL NÃO DEVEM SER CONSIDERADOS NESSE ITEM.
|
||||||
"REALIZEI ALGUMAS ALTERAÇÕES NO ROTEADOR PARA PADRONIZAR AS CONFIGURAÇÕES. PODES TESTAR A CONEXÃO NOVAMENTE?"
|
SOLICITAÇÃO OU CONFIRMAÇÃO DE TELEFONE NÃO DEVEM SER CONSIDERADOS NESSE ITEM.
|
||||||
"PARA DESCARTARMOS A POSSIBILIDADE DA ENTRADA ESTAR QUEIMADA, PODERIA COLOCAR O CABO NA OUTRA ENTRADA AMARELA?"
|
|
||||||
"PARA EVITAR TRAVAMENTOS E MELHORAR O DESEMPENHO, TERIA COMO REINICIAR OS EQUIPAMENTOS AGORA?"
|
|
||||||
"IDENTIFIQUEI QUE O PROBLEMA ESTÁ NO EQUIPAMENTO DA FIBRA, PODERIA CONFIRMAR SE O EQUIPAMENTO MENOR SEM ANTENAS ESTÁ COM UMA LUZ VERMELHA ACESA?"
|
|
||||||
"VERIFICANDO PELO SISTEMA, PARECE SE TRATAR DE UM PROBLEMA NA PARTE DA FIBRA, PODERIA VERIFICAR SE NO EQUIPAMENTO PEQUENO E SEM ANTENAS, POSSUI UMA LUZ VERMELHA PISCANDO?"
|
|
||||||
"ATÉ O EQUIPAMENTO DA FIBRA ESTÁ TUDO CERTO, O PROBLEMA PODE ESTAR NO ROTEADOR. QUANDO TU TENTAS TE CONECTAR AO WI-FI NO CELULAR, APARECE O NOME DA REDE NORMAL OU ALGO COMO 'TP-LINK'?"
|
|
||||||
"DE FORMA REMOTA EU IDENTIFIQUEI QUE O PROBLEMA ESTÁ NO APARELHO DA FIBRA. TU PODERIA ME CONFIRMAR SE O EQUIPAMENTO MENOR SEM ANTENAS ESTÁ COM UMA LUZ VERMELHA PISCANDO?"
|
|
||||||
"DE FORMA REMOTA EU IDENTIFIQUEI QUE O EQUIPAMENTO DA FIBRA NÃO ESTÁ RECEBENDO ENERGIA, PODERIA ME INFORMAR SE ESSE EQUIPAMENTO ESTÁ COM AS LUZES ACESAS?"
|
|
||||||
"REALIZEI ALGUMAS ALTERAÇÕES NO TEU ROTEADOR EM BUSCA DE DEIXAR AS CONFIGURAÇÕES PADRONIZADAS, TAMBÉM ALTEREI ALGUMAS CONFIGURAÇÕES REFERENTE AO SINAL E FREQUÊNCIA. PODERIA VERIFICAR COMO TUA CONEXÃO SE COMPORTA NO MOMENTO?"
|
|
||||||
❌ NÃO CONSIDERAR DIDÁTICO:
|
|
||||||
QUANDO O AGENTE PEDE ALGO SEM DIZER O MOTIVO.
|
|
||||||
FRASES VAGAS COMO: "RECOLOQUE OS CABOS", "DESLIGA E LIGA DE NOVO", "PODE VERIFICAR SE VOLTOU?"
|
|
||||||
NÃO CONSIDERAR A SOLICITAÇÃO DE E-MAIL COMO ALGO DIDATICO.
|
|
||||||
NÃO CONSIDERAR A CONFIRMAÇÃO DE E-MAIL COMO ALGO DIDATICO.
|
|
||||||
NÃO CONSIDERAR A SOLICITAÇÃO DE TELEFONE COMO ALGO DIDATICO.
|
|
||||||
NÃO CONSIDERAR A CONFIRMAÇÃO DE TELEFONE COMO ALGO DIDATICO.
|
|
||||||
|
|
||||||
10 (ECLARECIMENTO) - DURANTE A CONVERSA, O AGENTE DEVE FECHAR UM DIAGNOSTICO.
|
|
||||||
NÃO É NECESSÁRIO USAR AS MESMAS PALAVRAS DOS EXEMPLOS, BASTA DEIXAR O CLIENTE CIENTE DA CONCLUSÃO DO ATENDIMENTO.
|
|
||||||
✅ EXEMPLOS CORRETOS:
|
|
||||||
"COMO O NOME DA TUA REDE NÃO APARECE, ISSO INDICA QUE O ROTEADOR FOI RESETADO E VOLTOU ÀS CONFIGURAÇÕES DE FÁBRICA. SERÁ NECESSÁRIO ABRIR UMA ORDEM DE SERVIÇO PARA RECONFIGURAR O EQUIPAMENTO"
|
|
||||||
"TU ESTÁ RECEBENDO A VELOCIDADE CONTRATADA, MAS COMO O PLANO É LIMITADO, QUALQUER USO UM POUCO MAIS PESADO VAI AUMENTAR A LATÊNCIA, CAUSANDO TRAVAMENTOS E LENTIDÃO"
|
|
||||||
"NÃO ENCONTREI NENHUM PONTO DE ACESSO COM SINAL SUPERIOR PARA TUA ANTENA. SERÁ NECESSÁRIO ABRIR UMA ORDEM DE SERVIÇO PARA REALINHAR A ANTENA"
|
|
||||||
"DE FORMA REMOTA IDENTIFIQUEI QUE O EQUIPAMENTO DA FIBRA NÃO ESTÁ RECEBENDO ENERGIA. SERÁ NECESSÁRIO ABRIR UMA ORDEM DE SERVIÇO PARA QUE UM TÉCNICO VÁ AO LOCAL"
|
|
||||||
"SE A CONEXÃO ESTÁ SENDO USADA POR MUITAS PESSOAS AO MESMO TEMPO E ISSO FAZER COM QUE O CONSUMO SUPERE A VELOCIDADE CONTRATADA, A CONEXÃO PODERÁ APRESENTAR LENTIDÃO"
|
|
||||||
"EM ESPECÍFICO, QUANDO SE TRATA DE DISPOSITIVOS BOX, RECEPTORES DE CANAIS E APLICATIVOS IPTV, INFELIZMENTE NÃO CONSEGUIMOS GARANTIR ESTABILIDADE PARA ESSES TIPOS DE SERVIÇOS, JÁ QUE ELES NÃO DEPENDEM APENAS DE UMA BOA QUALIDADE DE INTERNET PARA FUNCIONAR CORRETAMENTE. VOU TE INDICAR A VERIFICAR A RESPEITO COM A PESSOA QUE TE FORNECEU O SERVIÇO, SE POSSÍVEL"
|
|
||||||
"O IDEAL SERIA CONECTAR TUA TV AO ROTEADOR POR UM CABO DE REDE, POIS ASSIM, O SINAL SERÁ TRANSMITIDO DIRETO, SEM SOFRER INTERFERÊNCIA.
|
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
As mensagens do chat estão estruturadas no formato JSON com os campos:
|
As mensagens do chat estão estruturadas no formato JSON com os campos:
|
||||||
@@ -66,10 +39,10 @@ O fluxo de mensagens inica-se com o cliente interagindo com o BOT, e então a me
|
|||||||
|
|
||||||
Em cada categoria, atribua 0 quando o agente não tiver atendido o critétio e 1 caso tenha atendido.
|
Em cada categoria, atribua 0 quando o agente não tiver atendido o critétio e 1 caso tenha atendido.
|
||||||
|
|
||||||
A sua resposta deve ser uma apenas uma tabela CSV e nada mais, utilizando como separador o caracter ';' com as colunas: CATEGORIA;PONTOS;MOTIVO;EVIDENCIA onde cada linha contém a avaliação de um dos critérios acima.
|
A sua resposta deve ser apenas uma tabela CSV e nada mais, utilizando como separador o caracter ';' com as colunas: CATEGORIA;PONTOS;MOTIVO;EVIDENCIA on>
|
||||||
|
|
||||||
|
Portando crie essas colunas para todo agente que for avaliado.
|
||||||
|
|
||||||
Na saída CSV, na coluna categoria, utilize o nome correspondente ao invés do número
|
Na saída CSV, na coluna categoria, utilize o nome correspondente ao invés do número
|
||||||
|
|
||||||
A seguir estão as mensagens do atendimento, em JSON, avalie e retorne apenas um CSV.
|
A seguir estão as mensagens do atendimento, em JSON, avalie e retorne apenas um CSV.
|
||||||
|
|
||||||
-----------------------------------
|
|
||||||
|
|||||||
@@ -32,6 +32,20 @@ struct CsvEvaluation {
|
|||||||
ID_TALK: String,
|
ID_TALK: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- ADICAO PARA AGRUPAR O response_time.csv ---
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct ResponseTimeRecord {
|
||||||
|
NOME: String,
|
||||||
|
ID_TALK: String,
|
||||||
|
#[serde(rename = "TEMPO DE RESPOSTA")]
|
||||||
|
TEMPO_DE_RESPOSTA: u32,
|
||||||
|
#[serde(rename = "TRANFERENCIA PELO BOT")]
|
||||||
|
TRANFERENCIA_PELO_BOT: String,
|
||||||
|
#[serde(rename = "PRIMEIRA RESPOSTA DO AGENTE")]
|
||||||
|
PRIMEIRA_RESPOSTA_DO_AGENTE: String,
|
||||||
|
}
|
||||||
|
// --- FIM DA ADIÇÃO ---
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
match dotenv::dotenv().ok() {
|
match dotenv::dotenv().ok() {
|
||||||
Some(_) => println!("Environment variables loaded from .env file"),
|
Some(_) => println!("Environment variables loaded from .env file"),
|
||||||
@@ -251,7 +265,7 @@ fn main() {
|
|||||||
// filename example is: CC - Erraoander Quintana - 515578 - 20251020515578.csv
|
// filename example is: CC - Erraoander Quintana - 515578 - 20251020515578.csv
|
||||||
// id talk is the last information, so in the example is: 20251020515578
|
// id talk is the last information, so in the example is: 20251020515578
|
||||||
let regex_filename =
|
let regex_filename =
|
||||||
regex::Regex::new(r"(CC - )((\w+\s*)+) - (\d+) - (\d+).csv").unwrap();
|
regex::Regex::new(r"(CC - |ATEND - )((\w+\s*)+) - (\d+) - (\d+).csv").unwrap();
|
||||||
|
|
||||||
let filename = file_path_csv
|
let filename = file_path_csv
|
||||||
.file_name()
|
.file_name()
|
||||||
@@ -369,12 +383,118 @@ fn main() {
|
|||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- ADICAO DO PROCESSAMENTO MENSAL DO response_time.csv ---
|
||||||
|
let response_times_data = previous_month_folder_names
|
||||||
|
.iter()
|
||||||
|
.map(|folder_name| {
|
||||||
|
let folder_base_path = std::path::Path::new("./evaluations");
|
||||||
|
let folder_date_path = folder_base_path.join(folder_name);
|
||||||
|
std::fs::read_dir(folder_date_path)
|
||||||
|
})
|
||||||
|
.filter_map_ok(|files_inside_folder_on_date| {
|
||||||
|
let response_time_files = files_inside_folder_on_date
|
||||||
|
.filter_ok(|entry| {
|
||||||
|
let entry_file_name_as_str = entry
|
||||||
|
.file_name()
|
||||||
|
.into_string()
|
||||||
|
.expect("Failed to get filename as a String");
|
||||||
|
|
||||||
|
entry_file_name_as_str.ends_with("response_time.csv")
|
||||||
|
})
|
||||||
|
.filter_map(|value| {
|
||||||
|
if value.is_ok() {
|
||||||
|
return Some(value.unwrap());
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.map(|file_path| {
|
||||||
|
println!("Processing response time file: {:?}", file_path.path());
|
||||||
|
|
||||||
|
let mut rdr = csv::ReaderBuilder::new()
|
||||||
|
.delimiter(b';')
|
||||||
|
.has_headers(true)
|
||||||
|
.from_reader(std::fs::File::open(file_path.path()).unwrap());
|
||||||
|
|
||||||
|
let records: Vec<ResponseTimeRecord> = rdr
|
||||||
|
.deserialize()
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
records
|
||||||
|
})
|
||||||
|
.flat_map(|records| records)
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
Some(response_time_files)
|
||||||
|
})
|
||||||
|
.filter_map(|res| {
|
||||||
|
if res.is_ok() {
|
||||||
|
return Some(res.unwrap());
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
})
|
||||||
|
.flat_map(|records| records)
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
// Salvar response times consolidados do mês
|
||||||
|
if !response_times_data.is_empty() {
|
||||||
|
let response_time_file_path = format!(
|
||||||
|
"./groupped/{first_day_of_last_month}/response_times_consolidated_{first_day_of_last_month}.csv"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut wtr = csv::WriterBuilder::new()
|
||||||
|
.delimiter(b';')
|
||||||
|
.from_path(&response_time_file_path)
|
||||||
|
.expect("Failed to create response times CSV");
|
||||||
|
|
||||||
|
// Escrever cabeçalho
|
||||||
|
wtr.write_record(&["NOME", "ID_TALK", "TEMPO DE RESPOSTA", "TRANFERENCIA PELO BOT", "PRIMEIRA RESPOSTA DO AGENTE"])
|
||||||
|
.expect("Failed to write header");
|
||||||
|
|
||||||
|
for record in &response_times_data {
|
||||||
|
wtr.write_record(&[
|
||||||
|
&record.NOME,
|
||||||
|
&record.ID_TALK,
|
||||||
|
&record.TEMPO_DE_RESPOSTA.to_string(),
|
||||||
|
&record.TRANFERENCIA_PELO_BOT,
|
||||||
|
&record.PRIMEIRA_RESPOSTA_DO_AGENTE,
|
||||||
|
]).expect("Failed to write record");
|
||||||
|
}
|
||||||
|
|
||||||
|
wtr.flush().expect("Failed to flush writer");
|
||||||
|
|
||||||
|
// Calcular estatísticas mensais
|
||||||
|
let total_records = response_times_data.len();
|
||||||
|
let avg_response_time = response_times_data.iter()
|
||||||
|
.map(|r| r.TEMPO_DE_RESPOSTA)
|
||||||
|
.sum::<u32>() as f32 / total_records as f32;
|
||||||
|
|
||||||
|
let min_response_time = response_times_data.iter()
|
||||||
|
.map(|r| r.TEMPO_DE_RESPOSTA)
|
||||||
|
.min()
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
let max_response_time = response_times_data.iter()
|
||||||
|
.map(|r| r.TEMPO_DE_RESPOSTA)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
println!("Response times consolidated successfully for month {}!", first_day_of_last_month);
|
||||||
|
println!("Total records: {}", total_records);
|
||||||
|
println!("Average response time: {:.2} seconds", avg_response_time);
|
||||||
|
println!("Min response time: {} seconds", min_response_time);
|
||||||
|
println!("Max response time: {} seconds", max_response_time);
|
||||||
|
} else {
|
||||||
|
println!("No response time data found for the month {}.", first_day_of_last_month);
|
||||||
|
}
|
||||||
|
// --- FIM DA ADIÇÃO ---
|
||||||
|
|
||||||
zip_directory_util::zip_directory_util::zip_source_dir_to_dst_file(
|
zip_directory_util::zip_directory_util::zip_source_dir_to_dst_file(
|
||||||
std::path::Path::new(&format!("./groupped/{first_day_of_last_month}")),
|
std::path::Path::new(&format!("./groupped/{first_day_of_last_month}")),
|
||||||
std::path::Path::new(&format!("./groupped/{first_day_of_last_month}.zip")),
|
std::path::Path::new(&format!("./groupped/{first_day_of_last_month}.zip")),
|
||||||
);
|
);
|
||||||
|
|
||||||
let recipients = "Wilson da Conceição Oliveira <wilson.oliveira@nova.net.br>, Isadora G. Moura de Moura <isadora.moura@nova.net.br>";
|
let recipients = "Wilson da Conceição Oliveira <wilson.oliveira@nova.net.br>, nicolas.borges@nova.net.br, Isadora G. Moura de Moura <isadora.moura@nova.net.br>";
|
||||||
println!("Trying to send mail... {recipients}");
|
println!("Trying to send mail... {recipients}");
|
||||||
send_mail_util::send_mail_util::send_email(
|
send_mail_util::send_mail_util::send_email(
|
||||||
&format!("Relatório agrupado dos atendimentos do mês {first_day_of_last_month}"),
|
&format!("Relatório agrupado dos atendimentos do mês {first_day_of_last_month}"),
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ use polars::prelude::*;
|
|||||||
use reqwest;
|
use reqwest;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use std::path::Path;
|
||||||
use csv;
|
use csv;
|
||||||
|
|
||||||
|
use std::fs::metadata;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
pub mod send_mail_util;
|
pub mod send_mail_util;
|
||||||
pub mod zip_directory_util;
|
pub mod zip_directory_util;
|
||||||
|
|
||||||
@@ -19,18 +22,32 @@ struct CsvHeader {
|
|||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
#[derive(Debug, serde::Deserialize)]
|
||||||
struct CsvEvaluation {
|
struct CsvEvaluation {
|
||||||
APRESENTAÇÃO: u8,
|
APRESENTACAO: u8,
|
||||||
CONFIRMAÇÃO_DE_EMAIL: u8,
|
CONFIRMAÇÃO_DE_EMAIL: u8,
|
||||||
CONFIRMAÇÃO_DE_TELEFONE: u8,
|
CONFIRMAÇÃO_DE_TELEFONE: u8,
|
||||||
PROTOCOLO: u8,
|
PROTOCOLO: u8,
|
||||||
USO_DO_PORTUGUÊS: u8,
|
USO_DO_PORTUGUES: u8,
|
||||||
PACIÊNCIA_E_EDUCAÇÃO: u8,
|
PACIENCIA_E_EDUCACAO: u8,
|
||||||
DISPONIBILIDADE: u8,
|
DISPONIBILIDADE: u8,
|
||||||
CONHECIMENTO_TÉCNICO: u8,
|
CONHECIMENTO_TÉCNICO: u8,
|
||||||
DIDATISMO: u8,
|
DIDATISMO: u8,
|
||||||
ID_TALK: String,
|
ID_TALK: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//inclusão de estrutura para agrupar o response_time.cvs
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct ResponseTimeRecord {
|
||||||
|
NOME: String,
|
||||||
|
ID_TALK: String,
|
||||||
|
#[serde(rename = "TEMPO DE RESPOSTA")]
|
||||||
|
TEMPO_DE_RESPOSTA: u32,
|
||||||
|
#[serde(rename = "TRANFERENCIA PELO BOT")]
|
||||||
|
TRANFERENCIA_PELO_BOT: String,
|
||||||
|
#[serde(rename = "PRIMEIRA RESPOSTA DO AGENTE")]
|
||||||
|
PRIMEIRA_RESPOSTA_DO_AGENTE: String,
|
||||||
|
}
|
||||||
|
//fim da inclusão
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
match dotenv::dotenv().ok() {
|
match dotenv::dotenv().ok() {
|
||||||
Some(_) => println!("Environment variables loaded from .env file"),
|
Some(_) => println!("Environment variables loaded from .env file"),
|
||||||
@@ -214,14 +231,93 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter_map_ok(|(ai_repsonse, file_path_csv)| {
|
.filter_map_ok(|(ai_response, file_path_csv)| {
|
||||||
let mut reader = csv::ReaderBuilder::new()
|
|
||||||
.has_headers(true)
|
// ---------- LOG 1: mostra qual arquivo está sendo processado ----------
|
||||||
.delimiter(b';')
|
// eprintln!("🔍 Processando arquivo: {:?}", file_path_csv);
|
||||||
.from_reader(ai_repsonse.as_bytes());
|
|
||||||
|
// Mostra o caminho absoluto
|
||||||
|
let path = file_path_csv.path();
|
||||||
|
|
||||||
|
// Caminho absoluto
|
||||||
|
if let Ok(abs_path) = std::fs::canonicalize(&path) {
|
||||||
|
eprintln!("📁 Caminho absoluto: {:?}", abs_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadados
|
||||||
|
if let Ok(meta) = std::fs::metadata(&path) {
|
||||||
|
if let Ok(modified) = meta.modified() {
|
||||||
|
let datetime: chrono::DateTime<chrono::Local> = modified.into();
|
||||||
|
eprintln!("📅 Modificado (local): {}", datetime.format("%Y-%m-%d %H:%M:%S%.3f"));
|
||||||
|
//eprintln!("🕒 Modificado: {:?}", modified);
|
||||||
|
}
|
||||||
|
eprintln!("📏 Tamanho: {} bytes", meta.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opcional: mostrar as primeiras linhas do CSV
|
||||||
|
eprintln!("📄 Primeiras 200 caracteres do CSV antes de sanitizar:\n{}", &ai_response[..200.min(ai_response.len())]);
|
||||||
|
//
|
||||||
|
|
||||||
|
// --- SALVAR CSV SANITIZADO PARA INSPEÇÃO ---
|
||||||
|
//let sanitized_path = file_path_csv.path().with_extension("sanitized.csv");
|
||||||
|
// if let Err(e) = std::fs::write(&sanitized_path, &ai_response) {
|
||||||
|
// eprintln!("⚠️ Erro ao salvar CSV sanitizado: {}", e);
|
||||||
|
//} else {
|
||||||
|
// eprintln!("💾 CSV sanitizado salvo: {:?}", sanitized_path);
|
||||||
|
// }
|
||||||
|
// ---------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
// --- SALVAR CSV SANITIZADO EM PASTA SEPARADA ---
|
||||||
|
// Define o diretório base para os arquivos sanitizados
|
||||||
|
let sanitized_base = Path::new("./evaluations_sanitized");
|
||||||
|
|
||||||
|
// Obtém o caminho relativo do arquivo original em relação a "./evaluations"
|
||||||
|
// Exemplo: "./evaluations/2026-02-09/arquivo.csv" -> "2026-02-09/arquivo.csv"
|
||||||
|
if let Ok(relative_path) = file_path_csv.path().strip_prefix("./evaluations") {
|
||||||
|
let dest_path = sanitized_base.join(relative_path);
|
||||||
|
|
||||||
|
// Cria o diretório de destino, se necessário
|
||||||
|
if let Some(parent) = dest_path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).expect("Falha ao criar diretório para sanitizados");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Altera a extensão para .sanitized.csv (ou mantém .csv, como preferir)
|
||||||
|
let dest_path = dest_path.with_extension("sanitized.csv");
|
||||||
|
|
||||||
|
// Escreve o arquivo
|
||||||
|
if let Err(e) = std::fs::write(&dest_path, &ai_response) {
|
||||||
|
eprintln!("⚠️ Erro ao salvar CSV sanitizado em {:?}: {}", dest_path, e);
|
||||||
|
} else {
|
||||||
|
eprintln!("💾 CSV sanitizado salvo em: {:?}", dest_path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("⚠️ Caminho do arquivo não está dentro de ./evaluations: {:?}", file_path_csv.path());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let mut reader = csv::ReaderBuilder::new()
|
||||||
|
.has_headers(true)
|
||||||
|
.delimiter(b';')
|
||||||
|
.from_reader(ai_response.as_bytes());
|
||||||
|
|
||||||
|
// ---------- LOG 2: tenta desserializar e conta os registros ----------
|
||||||
|
let deserialized = reader.deserialize::<CsvHeader>().collect::<Vec<_>>();
|
||||||
|
eprintln!("🧾 Total de linhas lidas (incluindo cabeçalho?): {}", deserialized.len());
|
||||||
|
|
||||||
|
for (i, result) in deserialized.iter().enumerate() {
|
||||||
|
match result {
|
||||||
|
Ok(record) => {
|
||||||
|
eprintln!("✅ Linha {}: CATEGORIA={}, PONTOS={:?}", i, record.CATEGORIA, record.PONTOS);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("❌ Linha {} ERRO: {}", i, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
let mut deserialized_iter = reader.deserialize::<CsvHeader>();
|
let mut deserialized_iter = reader.deserialize::<CsvHeader>();
|
||||||
|
|
||||||
let mut columns = deserialized_iter
|
let mut columns = deserialized_iter
|
||||||
.filter_ok(|value| value.PONTOS.is_some())
|
.filter_ok(|value| value.PONTOS.is_some())
|
||||||
.map_ok(|value| {
|
.map_ok(|value| {
|
||||||
@@ -236,16 +332,38 @@ fn main() {
|
|||||||
None
|
None
|
||||||
})
|
})
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
*/
|
||||||
|
let mut columns = deserialized
|
||||||
|
.into_iter() // usa os registros já lidos
|
||||||
|
.filter_ok(|value| {
|
||||||
|
// Se PONTOS for None, considera como 0 e mantém a linha
|
||||||
|
true // sempre mantém
|
||||||
|
})
|
||||||
|
.map_ok(|value| {
|
||||||
|
let pontos = value.PONTOS.unwrap_or(0) as u32;
|
||||||
|
Column::new(value.CATEGORIA.into(), [pontos])
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
.filter_ok(|value| value.PONTOS.is_some())
|
||||||
|
.map_ok(|value| {
|
||||||
|
let pontos = value.PONTOS.unwrap() as u32;
|
||||||
|
Column::new(value.CATEGORIA.into(), [pontos])
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
.filter_map(|value| value.ok())
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
|
||||||
if columns.len() != 9 {
|
if columns.len() != 9 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse id talk from file_path
|
// Parse id talk from file_path
|
||||||
// filename example is: CC - Erraoander Quintana - 515578 - 20251020515578.csv
|
// filename example is: FIN - Lais Mota - 515578 - 20251020515578.csv
|
||||||
// id talk is the last information, so in the example is: 20251020515578
|
// id talk is the last information, so in the example is: 20251020515578
|
||||||
let regex_filename =
|
let regex_filename =
|
||||||
regex::Regex::new(r"(CC - )((\w+\s*)+) - (\d+) - (\d+).csv").unwrap();
|
//regex::Regex::new(r"(FIN - )((\s*\w+\s*)+) - (\d+) - (\d+).csv").unwrap();
|
||||||
|
regex::Regex::new(r"ATEND - (.+?) - (\d+) - (\d+)\.csv").unwrap();
|
||||||
|
|
||||||
let filename = file_path_csv
|
let filename = file_path_csv
|
||||||
.file_name()
|
.file_name()
|
||||||
@@ -256,10 +374,10 @@ fn main() {
|
|||||||
.expect("Failed to do regex capture");
|
.expect("Failed to do regex capture");
|
||||||
|
|
||||||
let user_name = found_regex_groups_in_filename
|
let user_name = found_regex_groups_in_filename
|
||||||
.get(2)
|
.get(1)
|
||||||
.expect("Failed to get the id from regex maches");
|
.expect("Failed to get the id from regex maches");
|
||||||
let talk_id = found_regex_groups_in_filename
|
let talk_id = found_regex_groups_in_filename
|
||||||
.get(5)
|
.get(3)
|
||||||
.expect("Failed to get the id from regex maches");
|
.expect("Failed to get the id from regex maches");
|
||||||
|
|
||||||
let excelence_percentual = columns
|
let excelence_percentual = columns
|
||||||
@@ -369,6 +487,93 @@ fn main() {
|
|||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//inclusão nova para agrupar o response_time.csv
|
||||||
|
// Processar response_time.csv separadamente
|
||||||
|
let response_times_data = previous_week_folder_names
|
||||||
|
.iter()
|
||||||
|
.map(|folder_name| {
|
||||||
|
let folder_base_path = std::path::Path::new("./evaluations");
|
||||||
|
let folder_date_path = folder_base_path.join(folder_name);
|
||||||
|
std::fs::read_dir(folder_date_path)
|
||||||
|
})
|
||||||
|
.filter_map_ok(|files_inside_folder_on_date| {
|
||||||
|
let response_time_files = files_inside_folder_on_date
|
||||||
|
.filter_ok(|entry| {
|
||||||
|
let entry_file_name_as_str = entry
|
||||||
|
.file_name()
|
||||||
|
.into_string()
|
||||||
|
.expect("Failed to get filename as a String");
|
||||||
|
|
||||||
|
entry_file_name_as_str.ends_with("response_time.csv")
|
||||||
|
})
|
||||||
|
.filter_map(|value| {
|
||||||
|
if value.is_ok() {
|
||||||
|
return Some(value.unwrap());
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.map(|file_path| {
|
||||||
|
println!("Processing response time file: {:?}", file_path.path());
|
||||||
|
|
||||||
|
let mut rdr = csv::ReaderBuilder::new()
|
||||||
|
.delimiter(b';')
|
||||||
|
.has_headers(true)
|
||||||
|
.from_reader(std::fs::File::open(file_path.path()).unwrap());
|
||||||
|
|
||||||
|
let records: Vec<ResponseTimeRecord> = rdr
|
||||||
|
.deserialize()
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
records
|
||||||
|
})
|
||||||
|
.flat_map(|records| records)
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
Some(response_time_files)
|
||||||
|
})
|
||||||
|
.filter_map(|res| {
|
||||||
|
if res.is_ok() {
|
||||||
|
return Some(res.unwrap());
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
})
|
||||||
|
.flat_map(|records| records)
|
||||||
|
.collect_vec();
|
||||||
|
// Salvar response times consolidados
|
||||||
|
if !response_times_data.is_empty() {
|
||||||
|
let response_time_file_path = format!(
|
||||||
|
"./groupped/{first_day_of_last_week} - {last_day_of_last_week}/response_times_consolidated.csv"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut wtr = csv::WriterBuilder::new()
|
||||||
|
.delimiter(b';')
|
||||||
|
.from_path(response_time_file_path)
|
||||||
|
.expect("Failed to create response times CSV");
|
||||||
|
|
||||||
|
// Escrever cabeçalho
|
||||||
|
wtr.write_record(&["NOME", "ID_TALK", "TEMPO DE RESPOSTA", "TRANFERENCIA PELO BOT", "PRIMEIRA RESPOSTA DO AGENTE"])
|
||||||
|
.expect("Failed to write header");
|
||||||
|
|
||||||
|
for record in response_times_data {
|
||||||
|
wtr.write_record(&[
|
||||||
|
&record.NOME,
|
||||||
|
&record.ID_TALK,
|
||||||
|
&record.TEMPO_DE_RESPOSTA.to_string(),
|
||||||
|
&record.TRANFERENCIA_PELO_BOT,
|
||||||
|
&record.PRIMEIRA_RESPOSTA_DO_AGENTE,
|
||||||
|
]).expect("Failed to write record");
|
||||||
|
}
|
||||||
|
|
||||||
|
wtr.flush().expect("Failed to flush writer");
|
||||||
|
println!("Response times consolidated successfully!");
|
||||||
|
} else {
|
||||||
|
println!("No response time data found for the period.");
|
||||||
|
}
|
||||||
|
// --- FIM DA ADIÇÃO ---
|
||||||
|
|
||||||
|
//fim da inclusão
|
||||||
|
|
||||||
zip_directory_util::zip_directory_util::zip_source_dir_to_dst_file(
|
zip_directory_util::zip_directory_util::zip_source_dir_to_dst_file(
|
||||||
std::path::Path::new(&format!(
|
std::path::Path::new(&format!(
|
||||||
"./groupped/{first_day_of_last_week} - {last_day_of_last_week}"
|
"./groupped/{first_day_of_last_week} - {last_day_of_last_week}"
|
||||||
@@ -378,11 +583,11 @@ fn main() {
|
|||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let recipients = "Wilson da Conceição Oliveira <wilson.oliveira@nova.net.br>, Isadora G. Moura de Moura <isadora.moura@nova.net.br>";
|
let recipients = "Wilson da Conceição Oliveira <wilson.oliveira@nova.net.br>, nicolas.borges@nova.net.br, isadora.moura@nova.net.br";
|
||||||
println!("Trying to send mail... {recipients}");
|
println!("Trying to send mail... {recipients}");
|
||||||
send_mail_util::send_mail_util::send_email(
|
send_mail_util::send_mail_util::send_email(
|
||||||
&format!(
|
&format!(
|
||||||
"Relatório agrupado dos atendimentos semana {first_day_of_last_week} - {last_day_of_last_week}"
|
"Relatório agrupado dos atendimentos da fila do Suporte - semana {first_day_of_last_week} - {last_day_of_last_week}"
|
||||||
),
|
),
|
||||||
&BOT_EMAIL,
|
&BOT_EMAIL,
|
||||||
&BOT_EMAIL_PASSWORD,
|
&BOT_EMAIL_PASSWORD,
|
||||||
|
|||||||
70
src/main.rs
70
src/main.rs
@@ -1,5 +1,4 @@
|
|||||||
use std::{any::Any, env, fmt::format, iter, time::Duration};
|
use std::{any::Any, env, fmt::format, iter, time::Duration};
|
||||||
|
|
||||||
use chrono::{self, Timelike};
|
use chrono::{self, Timelike};
|
||||||
use dotenv;
|
use dotenv;
|
||||||
use ipaddress;
|
use ipaddress;
|
||||||
@@ -184,6 +183,53 @@ fn main() -> anyhow::Result<()> {
|
|||||||
.expect("Failed to 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<String> = env::args().collect();
|
||||||
|
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/{formatted_day}")).unwrap() {
|
||||||
|
std::fs::create_dir(format!("./evaluations/{formatted_day}"))
|
||||||
|
.expect("Failed to create directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Criar arquivo response_time.csv se não existir
|
||||||
|
if !std::fs::exists(format!("./evaluations/{formatted_day}/response_time.csv")).unwrap() {
|
||||||
|
let _ = std::fs::File::create_new(format!(
|
||||||
|
"./evaluations/{formatted_day}/response_time.csv"
|
||||||
|
))
|
||||||
|
.expect("Failed to create response_time.csv");
|
||||||
|
}
|
||||||
|
*/
|
||||||
// Read system prompt
|
// Read system prompt
|
||||||
let prompt = std::fs::read_to_string("PROMPT.txt").unwrap();
|
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_file_contents = std::fs::read_to_string("FILTER.txt").unwrap_or(String::new());
|
||||||
@@ -197,7 +243,9 @@ fn main() -> anyhow::Result<()> {
|
|||||||
&client,
|
&client,
|
||||||
&access_token,
|
&access_token,
|
||||||
formatted_day_before_at_midnight,
|
formatted_day_before_at_midnight,
|
||||||
|
//formatted_day_start,
|
||||||
formatted_day_before_at_23_59_59,
|
formatted_day_before_at_23_59_59,
|
||||||
|
//formatted_day_end,
|
||||||
);
|
);
|
||||||
|
|
||||||
println!("Number of consolidated talks: {}", talks_array.len());
|
println!("Number of consolidated talks: {}", talks_array.len());
|
||||||
@@ -298,12 +346,16 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let message = message_object["message"]
|
let message = message_object["message"]
|
||||||
.as_str()
|
.as_str()
|
||||||
.expect("Failed to decode message as string");
|
.expect("Failed to decode message as string");
|
||||||
let found = filter_keywords.iter().any(|keyword| {
|
let found1 = filter_keywords.iter().any(|keyword| {
|
||||||
message
|
message
|
||||||
.to_uppercase()
|
.to_uppercase()
|
||||||
.find(&keyword.to_uppercase())
|
.find(&keyword.to_uppercase())
|
||||||
.is_some()
|
.is_some()
|
||||||
});
|
});
|
||||||
|
let found2 = message_object["is_template"]
|
||||||
|
.as_bool()
|
||||||
|
.unwrap_or(true);
|
||||||
|
let found = found1 || found2;
|
||||||
found
|
found
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -421,6 +473,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
.write(true)
|
.write(true)
|
||||||
.open(format!(
|
.open(format!(
|
||||||
"./evaluations/{formatted_day_before}/response_time.csv"
|
"./evaluations/{formatted_day_before}/response_time.csv"
|
||||||
|
//"./evaluations/{formatted_day}/response_time.csv"
|
||||||
))
|
))
|
||||||
.expect("Failed to open response time file for write");
|
.expect("Failed to open response time file for write");
|
||||||
response_time_file
|
response_time_file
|
||||||
@@ -499,6 +552,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
format!(
|
format!(
|
||||||
"./evaluations/{}/{} - {} - {}.csv",
|
"./evaluations/{}/{} - {} - {}.csv",
|
||||||
formatted_day_before, user_name, talk_id, tracking_number
|
formatted_day_before, user_name, talk_id, tracking_number
|
||||||
|
//formatted_day, user_name, talk_id, tracking_number
|
||||||
),
|
),
|
||||||
csv_response,
|
csv_response,
|
||||||
)
|
)
|
||||||
@@ -507,6 +561,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
format!(
|
format!(
|
||||||
"./evaluations/{}/{} - {} - {} - prompt.txt",
|
"./evaluations/{}/{} - {} - {} - prompt.txt",
|
||||||
formatted_day_before, user_name, talk_id, tracking_number
|
formatted_day_before, user_name, talk_id, tracking_number
|
||||||
|
//formatted_day, user_name, talk_id, tracking_number
|
||||||
),
|
),
|
||||||
format!("{prompt} \n{talk}"),
|
format!("{prompt} \n{talk}"),
|
||||||
)
|
)
|
||||||
@@ -520,17 +575,20 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// Compress folder into zip
|
// Compress folder into zip
|
||||||
let source_dir_str = format!("./evaluations/{formatted_day_before}");
|
let source_dir_str = format!("./evaluations/{formatted_day_before}");
|
||||||
|
//let source_dir_str = format!("./evaluations/{formatted_day}");
|
||||||
let output_zip_file_str = format!("./evaluations/{formatted_day_before}.zip");
|
let output_zip_file_str = format!("./evaluations/{formatted_day_before}.zip");
|
||||||
|
//let output_zip_file_str = format!("./evaluations/{formatted_day}.zip");
|
||||||
let source_dir = std::path::Path::new(source_dir_str.as_str());
|
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());
|
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);
|
zip_directory_util::zip_directory_util::zip_source_dir_to_dst_file(source_dir, output_zip_file);
|
||||||
|
|
||||||
// Send folder to email
|
// Send folder to email
|
||||||
let recipients = "Wilson da Conceição Oliveira <wilson.oliveira@nova.net.br>, Isadora G. Moura de Moura <isadora.moura@nova.net.br>";
|
let recipients = "nicolas.borges@nova.net.br, Isadora G. Moura de Moura <isadora.moura@nova.net.br>";
|
||||||
println!("Trying to send email... Recipients {recipients}");
|
println!("Trying to send email... Recipients {recipients}");
|
||||||
|
|
||||||
send_mail_util::send_mail_util::send_email(
|
send_mail_util::send_mail_util::send_email(
|
||||||
&format!("Avaliacao atendimentos {formatted_day_before}"),
|
&format!("Avaliacao atendimentos {formatted_day_before}"),
|
||||||
|
//&format!("Avaliacao atendimentos {formatted_day}"),
|
||||||
&BOT_EMAIL,
|
&BOT_EMAIL,
|
||||||
&BOT_EMAIL_PASSWORD,
|
&BOT_EMAIL_PASSWORD,
|
||||||
recipients,
|
recipients,
|
||||||
@@ -545,7 +603,9 @@ fn get_piperun_chats_on_date(
|
|||||||
client: &reqwest::blocking::Client,
|
client: &reqwest::blocking::Client,
|
||||||
access_token: &String,
|
access_token: &String,
|
||||||
formatted_day_before_at_midnight: String,
|
formatted_day_before_at_midnight: String,
|
||||||
|
//formatted_day_start: String,
|
||||||
formatted_day_before_at_23_59_59: String,
|
formatted_day_before_at_23_59_59: String,
|
||||||
|
//formatted_day_end: String,
|
||||||
) -> Vec<serde_json::Value> {
|
) -> Vec<serde_json::Value> {
|
||||||
let start_of_talk_code: String = "talk_start".to_string();
|
let start_of_talk_code: String = "talk_start".to_string();
|
||||||
let support_queue_id: String = "13".to_string();
|
let support_queue_id: String = "13".to_string();
|
||||||
@@ -565,7 +625,9 @@ fn get_piperun_chats_on_date(
|
|||||||
("perPage", per_page.clone()),
|
("perPage", per_page.clone()),
|
||||||
("report_type", report_type.clone()),
|
("report_type", report_type.clone()),
|
||||||
("start_date", formatted_day_before_at_midnight.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_before_at_23_59_59.clone()),
|
||||||
|
//("end_date", formatted_day_end.clone()),
|
||||||
("date_range_type", start_of_talk_code.clone()),
|
("date_range_type", start_of_talk_code.clone()),
|
||||||
("queue_id[]", support_queue_id.clone()),
|
("queue_id[]", support_queue_id.clone()),
|
||||||
]);
|
]);
|
||||||
@@ -621,7 +683,9 @@ fn get_piperun_chats_on_date(
|
|||||||
("perPage", per_page.clone()),
|
("perPage", per_page.clone()),
|
||||||
("report_type", report_type.clone()),
|
("report_type", report_type.clone()),
|
||||||
("start_date", formatted_day_before_at_midnight.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_before_at_23_59_59.clone()),
|
||||||
|
//("end_date", formatted_day_end.clone()),
|
||||||
("date_range_type", start_of_talk_code.clone()),
|
("date_range_type", start_of_talk_code.clone()),
|
||||||
("queue_id[]", support_queue_id.clone()),
|
("queue_id[]", support_queue_id.clone()),
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user