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"

UCEM L-pI.219 (199) Revisão VI - Caso de uso da IA para estudar

  Lição 219  Eu não sou um corpo. Eu sou livre.  Pois ainda sou como Deus me criou. Lição anterior Escolher outra lição Próxima lição Texto ...