From 013731b59e97657c5ee0c352b98aae88b2c0c002 Mon Sep 17 00:00:00 2001 From: Davi Aliaga Date: Tue, 6 Oct 2026 11:08:26 -0300 Subject: [PATCH] =?UTF-8?q?fix/rpa:=20aprimora=20mapeamento=20de=20status?= =?UTF-8?q?=20de=20tarefas=20e=20ajusta=20l=C3=B3gica=20de=20datas=20de=20?= =?UTF-8?q?in=C3=ADcio=20e=20fim=20previstas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/dataload.ipynb | 59 +++++++++++++++++++++++++------------------ python/rpa.py | 35 ++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/python/dataload.ipynb b/python/dataload.ipynb index d5f2538..57d8902 100644 --- a/python/dataload.ipynb +++ b/python/dataload.ipynb @@ -21,8 +21,8 @@ "source": [ "# fazendo importações\n", "import os\n", - "from random import choice, choices, randint, triangular\n", - "from datetime import date, timedelta\n", + "from random import choice, choices, randint, triangular\n", + "from datetime import date, timedelta\n", "from dotenv import load_dotenv\n", "from faker import Faker\n", "import pandas as pd\n", @@ -57,18 +57,18 @@ "\n", "DATABASE_URL = os.getenv(\"URL_SEGUNDO_BANCO\")\n", "engine = create_engine(DATABASE_URL)\n", - "faker = Faker(\"pt_BR\")\n", - "hoje = date.today()\n", - "inicio_periodo = hoje - timedelta(days=730)\n", - "\n", - "def data_aleatoria(inicio=inicio_periodo, fim=hoje):\n", - " return faker.date_between_dates(date_start=inicio, date_end=fim)\n", - "\n", - "def data_prazo(inicio=inicio_periodo, fim=hoje):\n", - " data_base = data_aleatoria(inicio, fim)\n", - " if data_base < hoje and choice([True, False]):\n", - " return data_base + timedelta(days=randint(1, min(120, (hoje - data_base).days)))\n", - " return data_base + timedelta(days=randint(7, 120))" + "faker = Faker(\"pt_BR\")\n", + "hoje = date.today()\n", + "inicio_periodo = hoje - timedelta(days=730)\n", + "\n", + "def data_aleatoria(inicio=inicio_periodo, fim=hoje):\n", + " return faker.date_between_dates(date_start=inicio, date_end=fim)\n", + "\n", + "def data_prazo(inicio=inicio_periodo, fim=hoje):\n", + " data_base = data_aleatoria(inicio, fim)\n", + " if data_base < hoje and choice([True, False]):\n", + " return data_base + timedelta(days=randint(1, min(120, (hoje - data_base).days)))\n", + " return data_base + timedelta(days=randint(7, 120))" ] }, { @@ -338,7 +338,7 @@ " 'cargo': faker.job()[:100],\n", " 'area': choice(areas),\n", " 'data_nascimento': faker.date_of_birth(minimum_age=18, maximum_age=70),\n", - " 'data_contratacao': data_aleatoria(date(hoje.year - 10, 1, 1), hoje),\n", + " 'data_contratacao': data_aleatoria(date(hoje.year - 10, 1, 1), hoje),\n", " 'permissao_gestor': True if tipo == 'GESTOR' or tipo == 'ADMIN' else False,\n", " 'status': 'ATIVO'\n", " })\n", @@ -396,9 +396,9 @@ " 'titulo': faker.sentence(nb_words=5),\n", " 'descricao': faker.paragraph(nb_sentences=10),\n", " 'status': status,\n", - " 'data_inicio': (data_inicio := data_aleatoria()),\n", - " 'data_estimada_fim': data_prazo(data_inicio),\n", - " 'data_fim_real': data_aleatoria(data_inicio) if status == 'CONCLUIDO' else None,\n", + " 'data_inicio': (data_inicio := data_aleatoria()),\n", + " 'data_estimada_fim': data_prazo(data_inicio),\n", + " 'data_fim_real': data_aleatoria(data_inicio) if status == 'CONCLUIDO' else None,\n", " 'icone_url': choice(icone_ciclo_urls)\n", " })" ] @@ -446,7 +446,7 @@ " 'valor_base': randint(1000, 100000),\n", " 'valor_alvo': randint(1000, 100000),\n", " 'unidade': choice(['R$', 'US$', 'EUR']),\n", - " 'prazo': data_prazo(),\n", + " 'prazo': data_prazo(),\n", " 'status': choice(['PARCIALMENTE_ATINGIDA', 'ATINGIDA', 'NAO_ATINGIDA']),\n", " 'prioridade': choice(['BAIXA', 'MEDIA', 'ALTA', 'CRITICA']),\n", " 'area': choice(areas),\n", @@ -564,7 +564,7 @@ " 'what_acao': faker.sentence(nb_words=10),\n", " 'why_justificativa': faker.sentence(nb_words=10),\n", " 'where_local': faker.sentence(nb_words=10),\n", - " 'when_fim': data_prazo(),\n", + " 'when_fim': data_prazo(),\n", " 'how_modo_execucao': faker.sentence(nb_words=10),\n", " 'how_much_custo': randint(1000, 100000),\n", " })" @@ -593,14 +593,23 @@ "\n", "for id_plano_acao, plano_acao in enumerate(dados_plano_acao, start=1):\n", " for _ in range(randint(QTD_TAREFAS_MIN, QTD_TAREFAS_MAX)):\n", + " data_fim_prevista = data_prazo()\n", + " if choice([True, False]):\n", + " status_tarefa = choice(['CONCLUIDA', 'CANCELADA'])\n", + " elif data_fim_prevista < hoje:\n", + " status_tarefa = 'ATRASADA'\n", + " else:\n", + " status_tarefa = choice(['PENDENTE', 'EM_ANDAMENTO', 'BLOQUEADA'])\n", + "\n", " dados_tarefa.append({\n", " 'id_plano_acao': id_plano_acao,\n", " 'id_responsavel': plano_acao['criado_por'],\n", " 'titulo': faker.sentence(nb_words=5),\n", " 'descricao': faker.sentence(nb_words=10),\n", " 'prioridade': choice(['BAIXA', 'MEDIA', 'ALTA', 'CRITICA']),\n", - " 'status': choice(['PENDENTE', 'EM_ANDAMENTO', 'BLOQUEADA', 'CONCLUIDA', 'ATRASADA', 'CANCELADA']),\n", - " 'data_fim_prevista': data_prazo()\n", + " 'status': status_tarefa,\n", + " 'data_fim_prevista': data_fim_prevista,\n", + " 'data_fim_real': data_aleatoria() if status_tarefa == 'CONCLUIDA' else None\n", " })" ] }, @@ -631,7 +640,7 @@ " 'id_responsavel': ciclo['id_responsavel'],\n", " 'titulo': faker.sentence(nb_words=5),\n", " 'descricao': faker.sentence(nb_words=10),\n", - " 'data_treinamento': data_prazo(),\n", + " 'data_treinamento': data_prazo(),\n", " 'obrigatorio': choice([True, False])\n", " })" ] @@ -795,8 +804,8 @@ " 'id_treinamento': id_treinamento,\n", " 'id_usuario': id_usuario,\n", " 'obrigatorio': treinamento['obrigatorio'],\n", - " 'status': (status_treinamento := choice(['PENDENTE', 'CONFIRMADO', 'CONCLUIDO'])),\n", - " 'terminado_em': data_aleatoria() if status_treinamento == 'CONCLUIDO' else None\n", + " 'status': (status_treinamento := choice(['PENDENTE', 'CONFIRMADO', 'CONCLUIDO'])),\n", + " 'terminado_em': data_aleatoria() if status_treinamento == 'CONCLUIDO' else None\n", " })\n", "\n", "for problema in dados_problema:\n", diff --git a/python/rpa.py b/python/rpa.py index 06a80d5..a01e6e7 100644 --- a/python/rpa.py +++ b/python/rpa.py @@ -377,9 +377,36 @@ def carregar_tarefas(mapa_plano_ids, mapa_colaborador_ids): status_map = { "NAO_INICIADO": "PENDENTE", "INICIADO": "EM_ANDAMENTO", - "FINALIZADO": "CONCLUIDA" + "FINALIZADO": "CONCLUIDA", + "PENDENTE": "PENDENTE", + "EM_ANDAMENTO": "EM_ANDAMENTO", + "BLOQUEADA": "BLOQUEADA", + "CONCLUIDA": "CONCLUIDA", + "ATRASADA": "ATRASADA", + "CANCELADA": "CANCELADA", } + status_origem = df["status"].astype("string").str.strip().str.upper() + status_sem_mapeamento = status_origem.notna() & ~status_origem.isin(status_map) + if status_sem_mapeamento.any(): + desconhecidos = sorted(status_origem[status_sem_mapeamento].unique().tolist()) + raise ValueError(f"Status de tarefa sem mapeamento: {desconhecidos}") + + data_inicio_real = pd.to_datetime(df["dt_inicio"], errors="coerce") + data_fim_prevista = pd.to_datetime(df["dt_entrega"], errors="coerce") + data_fim_prevista = data_fim_prevista.fillna(data_inicio_real + pd.Timedelta(days=30)) + data_fim_prevista = data_fim_prevista.fillna(pd.Timestamp.now().normalize() + pd.Timedelta(days=30)) + data_fim_prevista = data_fim_prevista.where(data_inicio_real.isna() | data_fim_prevista.ge(data_inicio_real), data_inicio_real) + + status_destino = status_origem.map(status_map).fillna("PENDENTE") + tarefa_aberta = ~status_destino.isin(["CONCLUIDA", "CANCELADA"]) + + vencidas_em_aberto = data_fim_prevista.dt.normalize().lt(pd.Timestamp.now().normalize()) & tarefa_aberta + status_destino.loc[vencidas_em_aberto] = "ATRASADA" + + atraso_resolvido = status_destino.eq("ATRASADA") & ~vencidas_em_aberto + status_destino.loc[atraso_resolvido] = "EM_ANDAMENTO" + prioridade_map = { "BAIXO": "BAIXA", "MEDIO": "MEDIA", @@ -393,9 +420,9 @@ def carregar_tarefas(mapa_plano_ids, mapa_colaborador_ids): "titulo": df["titulo"].str.strip(), "descricao": df["descricao"].fillna("Sem descrição"), "prioridade": df["prioridade"].map(prioridade_map).fillna("MEDIA"), - "status": df["status"].map(status_map).fillna("PENDENTE"), - "data_inicio_real": df["dt_inicio"], - "data_fim_prevista": df["dt_entrega"].fillna(pd.to_datetime(df["dt_inicio"]) + pd.Timedelta(days=30)), + "status": status_destino, + "data_inicio_real": data_inicio_real, + "data_fim_prevista": data_fim_prevista, "criado_em": pd.Timestamp.now() })