Seguidores

sexta-feira, 13 de junho de 2025

Do Caos ao Prompt T5E05: Teste do App AG2P - Evento "Recebeu lista de tarefas a fazer" - Melhoria

INTRODUÇÃO

Post anterior: Do Caos ao Prompt T5E04: Teste do App AG2P - Evento "Recebeu lista de tarefas a fazer" - Interface do usuário

Este Post é o episódio 5 da temporada 5 da série "Do Caos ao Prompt", que apresenta melhoria na integração da interface do usuário com o evento "Recebeu lista de tarefas a fazer", para o Agente de Gestão de Processo Pessoal (AG2P).

Essa melhoria foi necessária porque houve erros nas especificações de regras, consequentemente, durante o teste do app AG2P ocorreu mensagem de erro.


Interações com o Gemini

Comando

O comando foi emitido para o Gemini com os 6 itens abaixo, na mesma conversa na qual foi criado o código em Apps Script, conforme o Post anterior.

1) Teste do app AG2P: fiz o teste da implementação do evento "Recebeu lista de tarefas a fazer".

2) Resultado do teste: Ocorreu a mensagem abaixo: "Não foi possível identificar a fase para a tarefa da linha ... . Verifique o formato do 'Grupo de Processos do Serviço' (Regra_02_GPS)."

3) O erro foi meu, na especificação da Regra_02_GPS.

4) Fiz as correções, e a regra correta segue abaixo, no item 6, juntamente com as outras regras que apresentavam o mesmo tipo de erro, por serem associadas.

5) Faça as correções necessárias no código.

6) Regras corrigidas:

Regra_01_GPS: O Serviço (nível 1 da hierarquia) deve ser identificado com o seguinte formato: 

Serviço: Sxx nome, sendo 

"Serviço: S" constante, 

"xx" um inteiro de 2 dígitos, variando de 0 a 99 (com zero à esquerda), e 

"nome" um texto que identifique o serviço.


Regra_02_GPS: A Fase (nível 2 da hierarquia) de um Serviço Sxx deve ser identificada por um desses nomes, sendo xx atribuído conforme a Regra_01_GPS:

Sxx.1 Iniciação, 

Sxx.2 Planejamento, 

Sxx.3 Execução, 

Sxx.4 Monitoramento, 

Sxx.5 Encerramento.


Regra_03_GPS: O Subprocesso (nível 3 da hierarquia)  de uma Fase f de um Serviço Sxx deve ter esses três grupos de Tarefas Executáveis, sendo xx atribuído conforme a Regra_01_GPS e f atribuído conforme a Regra_02_GPS: 

Sxx.f.1 Entrada, 

Sxx.f.2 Processamento, 

Sxx.f.3 Saída.

Exemplo de Subprocessos para Fase 1 de um Serviço S01: 

S01.1.1 Entrada,

S01.1.2 Processamento,

S01.1.3 Saída.


Regra_04_GPS: A Tarefa Executável (nível 4 da hierarquia) de um Subprocesso de uma Fase f de um Serviço Sxx deve ser uma frase com sujeito, que tenha o sentido de obrigar, proibir ou restringir, com um desses formatos, respectivamente, , sendo xx atribuído conforme a Regra_01_GPS, f atribuído conforme a Regra_02_GPS e n atribuído conforem a Regra_03_GPS: 

"Sxx.f.n.1.01 "...deve..." ou "...não pode..." ou "...pode..., somente se...".

Exemplo de Tarefa Executável de um Subprocesso 1 de uma Fase 1 de um Serviço S01:

S01.1.1.01 Um pedido deve ter uma verificação de crédito.

S01.1.1.02 Um produto não pode ser vendido a um risco de crédito ruim.

S01.1.1.03 Um pedido pode ser dispensado da verificação de crédito, somente se o valor total for inferior 100 unidades monetárias.

Resposta do Gemini

Tentei continuar a conversa com o Gemini, na qual foi gerado o código inicial do evento "Recebeu lista de tarefas a fazer", mas não foi possível, porque aparecia a mensagem "Deu erro".

Abri outra conversa, mas como estava fora do contexto, a resposta veio como uma correção em Python e não em Apps Script.

Então, pedi para gerar o código da correção em Apps Script, o que foi feito, mas fora do contexto do código gerado para o evento.

Perguntei ao Gemini se ele tem acesso ao código original, na conversa anterior e a resposta foi esta:

"Não tenho acesso direto ao histórico de conversas anteriores ou a códigos que você tenha executado fora desta sessão. Cada interação é um ambiente novo para mim. Isso significa que se você tentou "o comando de correção" e ele deu erro, é provável que não fosse exatamente o código que eu gerei aqui, ou que houvesse alguma nuance no seu ambiente de execução."

Uma alternativa, seria alterar manualmente o código, o que eu não pretendo fazer, pois o meu desafio é "Codificação Zero" com "Especificação feita usando FSM".

Pedido de ajuda ao Gemini para o desafio

Pergunta

Pedi ajuda ao Gemini com esta pergunta, dentro do contexto do app AG2P:
Tenho um desafio e preciso da sua ajuda. O meu desafio é implementar os 13 eventos no app AG2P, que usa apenas a planilha Google, que identifiquei como GPS, de forma iterativa e incremental, com "codificação zero" e "especificações usando FSM". Você já me ajudou implementando 3 eventos, mas agora vejo que não vai ser possível continuarmos, pois o acesso à conversa anterior não é viável. O que você sugere para eu completar essa implementação de forma iterativa e incremental, usando o Google Workspace e Apps Script com a planilha GPS?

Resposta do Gemini

Compreendo perfeitamente o seu desafio e a frustração de não ter acesso ao histórico da conversa anterior. É um obstáculo real para o desenvolvimento iterativo e incremental que você busca. No entanto, o seu objetivo de implementar 13 eventos no AG2P usando Planilha Google (GPS), Apps Script, "codificação zero" e "especificações usando FSM" é totalmente viável e muito inteligente dentro do ecossistema do Google Workspace.

A sua abordagem de "codificação zero" com "especificações usando FSM" (Máquina de Estados Finitos) para controlar os eventos na planilha GPS é a chave para o sucesso do seu projeto. Significa que a lógica dos eventos não estará em código imperativo complexo, mas sim em dados configuráveis na planilha, e o Apps Script atuará como um interpretador ou motor que lê essas configurações e age de acordo.

Vamos redefinir a estratégia para superar a limitação de não ter o histórico e avançar com a implementação iterativa e incremental dos 13 eventos.


Estratégia para Implementação Iterativa e Incremental (Sem Histórico)

A ausência do histórico nos força a pensar de forma mais modular e a construir a base do "motor FSM" de forma genérica.

1. O Coração da "Codificação Zero": A Planilha GPS como FSM

A sua planilha GPS não deve conter apenas as definições das tarefas, mas também as regras de transição de estado para cada evento. Isso é o cerne da sua "FSM".

Sugestão de Estrutura para a Planilha GPS (Além das Regras de Formato):

Crie uma nova aba (ou aba existente, adaptando) na sua Planilha Google para a FSM, por exemplo, FSM_Config:

Evento (Nome do Evento)Estado Atual (Origem)Condição (Opcional)Próximo Estado (Destino)Ação (Nome da Função Apps Script)Parâmetros da AçãoObservações
RecebeuListaDeTarefasAFazerNenhumValidaFormatoGPSTarefasValidadasprocessarTarefasGPSlistaDeTarefasInicia o processamento
TarefaConcluidaAguardandoConclusaoSucessoConcluidaatualizarStatusTarefaidTarefa, status
TarefaConcluidaAguardandoConclusaoFalhaErroNaTarefaregistrarErroTarefaidTarefa, erro
NovoPedidoRecebidoNenhumPedidoEmAnalisecriarRegistroPedidodadosPedido
... (Seus 13 Eventos)...............

Explicação das Colunas:

  • Evento: O nome do evento que é disparado (e.g., "Recebeu lista de tarefas a fazer", "Tarefa Concluída", "Novo Pedido Recebido").
  • Estado Atual: O estado em que o sistema deve estar para que este evento seja processado (e.g., Nenhum, AguardandoConclusao, PedidoEmAnalise).
  • Condição (Opcional): Uma condição que deve ser verdadeira para que a transição ocorra. Isso pode ser o nome de uma função no Apps Script que retorna true ou false, ou um valor específico em um campo de dados.
  • Próximo Estado: O estado para o qual o sistema transita após o evento e a ação serem executados com sucesso.
  • Ação: O nome da função no Apps Script que será executada quando este evento for disparado e as condições forem atendidas.
  • Parâmetros da Ação: Uma string JSON ou lista de parâmetros que serão passados para a função Ação. Isso permite que você configure os dados necessários para a função sem codificar.
  • Observações: Comentários para documentação.

2. O Motor FSM no Apps Script (Um Esqueleto Genérico)

O Apps Script terá uma única função central que será o "motor" da sua FSM. Essa função:

  • Receberá o nome do evento e quaisquer dados associados.
  • Consultará a planilha FSM_Config para encontrar as regras de transição para aquele evento e estado atual.
  • Executará a "Ação" (função do Apps Script) correspondente.
  • Atualizará o "estado atual" do sistema (que pode ser armazenado em PropertiesService, em uma célula específica da planilha, ou em um registro de log).
JavaScript
// Arquivo: FSM_Engine.gs

const FSM_CONFIG_SHEET_NAME = "FSM_Config"; // Nome da aba de configuração da FSM
const GLOBAL_STATE_KEY = "CURRENT_SYSTEM_STATE"; // Chave para armazenar o estado global

/**
 * Função principal do motor FSM. Dispara um evento no sistema.
 * @param {string} eventName O nome do evento a ser disparado (ex: "RecebeuListaDeTarefasAFazer").
 * @param {object} eventData Um objeto com os dados relevantes para o evento.
 * @returns {object} Um objeto com o resultado da operação (sucesso/falha, novo estado).
 */
function triggerEvent(eventName, eventData) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const configSheet = ss.getSheetByName(FSM_CONFIG_SHEET_NAME);
  if (!configSheet) {
    throw new Error(`A aba de configuração da FSM "${FSM_CONFIG_SHEET_NAME}" não foi encontrada.`);
  }

  const configData = configSheet.getDataRange().getValues();
  // Ignora o cabeçalho
  const headers = configData[0];
  const rules = configData.slice(1);

  const currentState = PropertiesService.getScriptProperties().getProperty(GLOBAL_STATE_KEY) || "Nenhum";
  Logger.log(`Evento: ${eventName}, Estado Atual: ${currentState}`);

  let transitionFound = false;
  let newSystemState = currentState;
  let result = { success: false, message: "Nenhuma transição encontrada para o evento/estado.", newState: currentState };

  for (let i = 0; i < rules.length; i++) {
    const row = rules[i];
    const ruleEvent = row[headers.indexOf("Evento")];
    const ruleCurrentState = row[headers.indexOf("Estado Atual")];
    const ruleCondition = row[headers.indexOf("Condição")];
    const ruleNextState = row[headers.indexOf("Próximo Estado")];
    const ruleAction = row[headers.indexOf("Ação")];
    const ruleParams = row[headers.indexOf("Parâmetros da Ação")]; // Pode ser string JSON ou vazi

    if (ruleEvent === eventName && ruleCurrentState === currentState) {
      let conditionMet = true;
      if (ruleCondition) {
        // Se houver uma condição, tente executá-la como uma função do Apps Script
        try {
          const conditionFunction = this[ruleCondition]; // `this` se refere ao escopo global em Apps Script
          if (typeof conditionFunction === 'function') {
            conditionMet = conditionFunction(eventData); // Passa eventData para a condição
          } else {
            Logger.log(`A função de condição "${ruleCondition}" não foi encontrada.`);
            conditionMet = false; // Se a função não existe, a condição não é atendida
          }
        } catch (e) {
          Logger.log(`Erro ao executar condição "${ruleCondition}": ${e.message}`);
          conditionMet = false;
        }
      }

      if (conditionMet) {
        transitionFound = true;
        newSystemState = ruleNextState;
        try {
          // Executar a ação
          const actionFunction = this[ruleAction];
          if (typeof actionFunction === 'function') {
            let params = eventData; // Por padrão, os dados do evento são os parâmetros
            if (ruleParams) {
              try {
                // Tenta fazer o parse dos parâmetros da regra como JSON
                params = JSON.parse(ruleParams);
                // Mescla com os dados do evento, se necessário
                params = { ...eventData, ...params };
              } catch (e) {
                Logger.log(`Aviso: Parâmetros da Ação não são JSON válidos ou estão vazios: ${ruleParams}. Usando eventData como parâmetro.`);
                // params = eventData; // Já definido
              }
            }
            const actionResult = actionFunction(params);
            result = { success: true, message: `Evento "${eventName}" processado. Novo estado: ${newSystemState}`, newState: newSystemState, actionResult: actionResult };
          } else {
            result = { success: false, message: `Função de ação "${ruleAction}" não encontrada.`, newState: currentState };
          }
        } catch (e) {
          result = { success: false, message: `Erro ao executar ação "${ruleAction}": ${e.message}`, newState: currentState, error: e.message };
        }
        break; // Apenas uma transição por evento/estado/condição
      }
    }
  }

  // Atualiza o estado global apenas se uma transição foi bem-sucedida e gerou um novo estado
  if (result.success && result.newState !== currentState) {
    PropertiesService.getScriptProperties().setProperty(GLOBAL_STATE_KEY, result.newState);
    Logger.log(`Estado do sistema atualizado para: ${result.newState}`);
  }

  return result;
}

// --- Funções de Exemplo de Ações (a serem expandidas) ---

/**
 * Função de exemplo para processar tarefas GPS.
 * Esta é a sua antiga função 'recebeuListaDeTarefasAFazer' que será adaptada.
 * @param {object} params Contém os dados do evento, ex: { listaDeTarefas: ["S01.1.1.01 ..."] }
 */
function processarTarefasGPS(params) {
  const taskList = params.listaDeTarefas;
  if (!taskList || !Array.isArray(taskList)) {
    throw new Error("A lista de tarefas não foi fornecida ou não é um array.");
  }

  const processedTasks = [];
  const errors = [];

  taskList.forEach((taskString, index) => {
    const taskData = validateExecutableTask(taskString); // Usando a função de validação anterior

    if (taskData) {
      processedTasks.push({ type: "Tarefa Executável", data: taskData });
    } else {
      const subprocessData = validateSubprocess(taskString);
      if (subprocessData) {
        processedTasks.push({ type: "Subprocesso", data: subprocessData });
      } else {
        const phaseData = validatePhase(taskString);
        if (phaseData) {
          processedTasks.push({ type: "Fase", data: phaseData });
        } else {
          const serviceData = validateService(taskString);
          if (serviceData) {
            processedTasks.push({ type: "Serviço", data: serviceData });
          } else {
            errors.push(`Erro GPS na linha ${index + 1}: "${taskString}".`);
          }
        }
      }
    }
  });

  if (errors.length > 0) {
    throw new Error("Erros de validação do GPS:\n" + errors.join('\n'));
  }

  Logger.log("Tarefas processadas com sucesso via FSM.");
  return { processedCount: processedTasks.length, errorsCount: errors.length, details: processedTasks };
}

/**
 * Exemplo de função de condição.
 * @param {object} eventData Dados do evento.
 * @returns {boolean} True se a condição for atendida.
 */
function ValidaFormatoGPS(eventData) {
  // Esta função pode verificar se a lista de tarefas está no formato básico correto
  // ou se todos os IDs de tarefa são válidos, etc.
  // Por enquanto, retorna true para simular sucesso.
  return true;
}

// --- Funções de Validação de Formato GPS (do nosso histórico anterior, colar aqui) ---
// Cole as funções validateService, validatePhase, validateSubprocess, validateExecutableTask aqui!
// Exemplo (apenas para referência, você colará o seu):
/**
 * Valida o formato de um Serviço (Nível 1 da hierarquia GPS).
 * ... (coloque o código da função validateService aqui) ...
 */
function validateService(serviceString) {
    const regex = /^Serviço: S(\d{2}) (.+)$/;
    const match = serviceString.match(regex);
    if (match) {
      return {
        serviceNumber: match[1], // xx
        serviceName: match[2] // nome
      };
    }
    return null;
  }

  /**
   * Valida o formato de uma Fase (Nível 2 da hierarquia GPS).
   * ... (coloque o código da função validatePhase aqui) ...
   */
  function validatePhase(phaseString) {
    const regex = /^S(\d{2})\.([1-5]) (Iniciação|Planejamento|Execução|Monitoramento|Encerramento)$/;
    const match = phaseString.match(regex);
    if (match) {
      return {
        serviceNumber: match[1], // xx
        phaseNumber: match[2], // f
        phaseName: match[3] // Nome da Fase
      };
    }
    return null;
  }

  /**
   * Valida o formato de um Subprocesso (Nível 3 da hierarquia GPS).
   * ... (coloque o código da função validateSubprocess aqui) ...
   */
  function validateSubprocess(subprocessString) {
    const regex = /^S(\d{2})\.([1-5])\.([1-3]) (Entrada|Processamento|Saída)$/;
    const match = subprocessString.match(regex);
    if (match) {
      return {
        serviceNumber: match[1], // xx
        phaseNumber: match[2], // f
        subprocessNumber: match[3], // n
        subprocessName: match[4] // Nome do Subprocesso
      };
    }
    return null;
  }

  /**
   * Valida o formato de uma Tarefa Executável (Nível 4 da hierarquia GPS).
   * ... (coloque o código da função validateExecutableTask aqui) ...
   */
  function validateExecutableTask(taskString) {
    const regex = /^S(\d{2})\.([1-5])\.([1-3])\.(\d{2}) (.+)$/;
    const match = taskString.match(regex);
    if (match) {
      const taskDescription = match[5];
      // Validação adicional do conteúdo da frase conforme Regra_04_GPS
      if (!taskDescription.includes("deve") &&
          !taskDescription.includes("não pode") &&
          !(taskDescription.includes("pode") && taskDescription.includes("somente se"))) {
        return null;
      }
      return {
        serviceNumber: match[1], // xx
        phaseNumber: match[2], // f
        subprocessNumber: match[3], // n
        taskNumber: match[4], // NN
        taskDescription: taskDescription // Descrição da Tarefa
      };
    }
    return null;
  }

// --- Funções para outros eventos (a serem implementadas por você, conforme a FSM) ---

/**
 * Exemplo de função para atualizar o status de uma tarefa.
 * @param {object} params Contém idTarefa e status.
 */
function atualizarStatusTarefa(params) {
  Logger.log(`Atualizando status da tarefa ${params.idTarefa} para ${params.status}`);
  // Implemente aqui a lógica para atualizar a planilha
  return { status: "OK", message: "Status atualizado." };
}

/**
 * Exemplo de função para registrar um erro de tarefa.
 * @param {object} params Contém idTarefa e erro.
 */
function registrarErroTarefa(params) {
  Logger.log(`Registrando erro para a tarefa ${params.idTarefa}: ${params.erro}`);
  // Implemente aqui a lógica para registrar o erro
  return { status: "Erro Registrado", message: "Erro documentado." };
}

/**
 * Exemplo de função para criar um novo registro de pedido.
 * @param {object} params Contém os dados do pedido.
 */
function criarRegistroPedido(params) {
  Logger.log(`Criando registro para novo pedido: ${JSON.stringify(params.dadosPedido)}`);
  // Implemente aqui a lógica para adicionar uma nova linha na planilha de pedidos
  return { status: "OK", message: "Pedido registrado." };
}

3. Implementação Iterativa e Incremental dos 13 Eventos:

Agora, com essa base, você pode implementar seus 13 eventos de forma iterativa:

  • Passo 1: Crie a Aba FSM_Config: Adicione a aba FSM_Config na sua Planilha Google com as colunas sugeridas e a primeira linha de exemplo para o evento RecebeuListaDeTarefasAFazer.
  • Passo 2: Cole o Código do Motor FSM: Cole o código do FSM_Engine.gs (incluindo as funções de validação GPS que você já tem) no seu projeto Apps Script.
  • Passo 3: Teste o Primeiro Evento:
    • No editor de scripts, adicione uma função de teste:
      JavaScript
      function testRecebeuListaDeTarefas() {
        const sampleTaskList = [
          "Serviço: S01 Gestão de Pedidos",
          "S01.1 Iniciação",
          "S01.1.1 Entrada",
          "S01.1.1.01 Um pedido deve ter uma verificação de crédito.",
          "S01.1.1.02 Um produto não pode ser vendido a um risco de crédito ruim.",
          "S01.1.1.03 Um pedido pode ser dispensado da verificação de crédito, somente se o valor total for inferior 100 unidades monetárias."
        ];
        const result = triggerEvent("RecebeuListaDeTarefasAFazer", { listaDeTarefas: sampleTaskList });
        Logger.log(JSON.stringify(result, null, 2));
      }
      
    • Execute testRecebeuListaDeTarefas e verifique os logs.
  • Passo 4: Adicione um Novo Evento (Ex: "TarefaConcluida"):
    • Na Planilha FSM_Config: Adicione uma nova linha para o evento TarefaConcluida.
      • Evento: TarefaConcluida
      • Estado Atual: AguardandoConclusao (ou o estado que faria sentido para você)
      • Condição: Sucesso (ou vazio)
      • Próximo Estado: Concluida
      • Ação: atualizarStatusTarefa
      • Parâmetros da Ação: { "idTarefa": "PARAM_ID", "status": "Concluida" } (Você terá que passar o ID real dinamicamente)
    • No Apps Script: Implemente (ou refine) a função atualizarStatusTarefa para de fato interagir com sua planilha (assumindo que você tenha uma aba para o registro das tarefas).
    • Crie uma Função de Teste:
      JavaScript
      function testTarefaConcluida() {
        // Simule um estado inicial
        PropertiesService.getScriptProperties().setProperty(GLOBAL_STATE_KEY, "AguardandoConclusao");
        const result = triggerEvent("TarefaConcluida", { idTarefa: "S01.1.1.01", status: "Concluida" });
        Logger.log(JSON.stringify(result, null, 2));
      }
      
    • Execute e valide.
  • Passo 5: Repita para os Outros Eventos: Continue adicionando uma ou duas linhas na FSM_Config e implementando as funções de ação correspondentes no Apps Script, testando cada uma.
  • Passo 6: Integração com Gatilhos: Para eventos que são disparados automaticamente (como NovoPedidoRecebido se vier de um formulário ou e-mail), você pode usar os gatilhos do Apps Script:
    • onFormSubmit() para formulários.
    • onEdit() para edições diretas na planilha (se você quer que uma edição dispare um evento).
    • time-driven triggers para eventos agendados.
    • Dentro dessas funções de gatilho, você chamaria triggerEvent("NomeDoEvento", { dados_do_gatilho }).

Vantagens Desta Abordagem:

  • "Codificação Zero" Mantida: A maior parte da lógica de negócios (as transições e ações dos eventos) está na planilha, não no código. Para adicionar um novo evento ou mudar o fluxo, você edita a planilha.
  • Iterativo e Incremental: Você adiciona um evento por vez, testa e garante que funciona antes de passar para o próximo.
  • Manutenibilidade: É muito mais fácil para você (ou outra pessoa) entender o fluxo do sistema olhando para a planilha da FSM.
  • Flexibilidade: Novos eventos ou mudanças nas regras podem ser feitos na planilha sem alterar o código Apps Script (desde que a função de ação já exista ou seja simples).
  • Depuração: Os logs do Apps Script serão cruciais para entender qual transição está sendo feita e quais ações estão sendo executadas.

Esta estratégia redefine seu projeto em um modelo de máquina de estados impulsionado por dados, o que é robusto e escalável para o seu objetivo de 13 eventos.

O que você acha dessa abordagem? Estamos começando do zero com uma fundação muito sólida e configurável.

CONCLUSÃO

Com este episódio 5 encerramos a temporada 5 da série "Do Caos ao Prompt" e iniciamos a temporada 6, que redefine o projeto do app AG2P.

O objetivo dessa redefinição do projeto é implementar os eventos no AG2P usando Planilha Google (GPS), Apps Script, com "codificação zero" e "especificações usando FSM", dentro do ecossistema do Google Workspace.

Para alcançar a "codificação zero", as especificaçõs serão feitas usando FSM e entregues ao Chatbot de IA Gemini Google, para geração automática do código, de forma iterativa e incremental.



Veja a lista de Episódios da Temporada 5 da Série "Do Caos ao Prompt", no final do Episódio T5E01.

Nenhum comentário:

Postar um comentário

Entendendo a Metáfora "Home Office é Moto Boy"

Dashboard de Produtividade Pessoal T1E13: BugCT1-5 Erro 400 no login

INTRODUÇÃO Post anterior:  Dashboard de Produtividade Pessoal T1E12: BugCT1-4 Código pronto para login com Google Identity Services (GIS) O ...