1. A migração no projeto de implantação
A aula anterior tratou das regras que governam a operação e de onde cada uma deve ser implementada no sistema de gestão. Esta aula trata do insumo sem o qual nenhuma regra pode ser exercida: os dados. Um sistema corretamente configurado, quando alimentado com cadastro incompleto ou incorreto, executa decisões incorretas de forma igualmente automática.
A migração de dados é frequentemente tratada como tarefa de infraestrutura, executada nos dias que antecedem a entrada em operação. Essa leitura é a origem de boa parte dos atrasos observados em implantação. A carga propriamente dita é um procedimento breve; o trabalho que a viabiliza — extrair, medir, corrigir, mapear e validar — é análise de dados, consome semanas e depende de decisões que somente a área de negócio pode tomar.
Adota-se, ao longo desta aula, o caso já apresentado na Aula 1: a distribuidora de peças de reposição industrial, que está implantando um sistema de gestão empresarial e precisa transferir, do sistema legado, o cadastro de materiais e os saldos de estoque. O extrato disponível é uma planilha com duas abas — condição frequente em empresas de médio porte, em que o sistema legado não oferece exportação estruturada.
Examinar a base legada apenas quando o ambiente de destino já está configurado. Nesse momento, os defeitos de cadastro deixam de ser problema de dados e passam a ser problema de cronograma, porque não há mais folga para as decisões de negócio que a correção exige.
Ao final da preparação, o grupo deve ser capaz de apresentar três artefatos:
- O arquivo de carga, no formato exigido pelo objeto de migração de destino.
- O relatório de exceções, com todo registro rejeitado e o motivo da rejeição.
- A reconciliação, que confronta contagens e somatórios entre a origem e o destino.
A ausência de qualquer um deles impede afirmar que a carga foi bem-sucedida: sem exceções não se sabe o que ficou de fora; sem reconciliação não se sabe se o que entrou corresponde ao que existia.
2. O ciclo da migração de dados
O trabalho organiza-se em sete etapas, percorridas integralmente a cada ensaio de carga. O ciclo é iterativo: as exceções produzidas na reconciliação realimentam a limpeza.
do legado] --> B[Perfilagem
diagnóstico] B --> C[Limpeza e
enriquecimento] C --> D[Mapeamento
de-para] D --> E[Transformação
ao layout de destino] E --> F[Carga
no ambiente] F --> G[Reconciliação
e aceite] G -.->|exceções| C
| Etapa | Produto | Responsabilidade |
|---|---|---|
| Extração | Extrato do sistema legado, com data e hora de corte registradas. | Equipe do sistema legado, com acompanhamento do projeto. |
| Perfilagem | Diagnóstico quantitativo por campo e por registro. | Equipe de dados do projeto. |
| Limpeza | Base corrigida e lista de decisões pendentes. | Equipe de dados; decisões cabem ao dono do dado. |
| Mapeamento | Tabelas de correspondência entre valores de origem e de destino. | Área de negócio, com apoio da consultoria funcional. |
| Transformação | Arquivo no layout do objeto de migração. | Equipe de dados. |
| Carga | Registros criados no ambiente, com registro de erros. | Equipe funcional e técnica. |
| Reconciliação | Confronto de totais e aceite formal. | Dono do dado na área de negócio. |
A regra tem justificativa operacional. Entre o primeiro ensaio e o cutover, o extrato é gerado novamente diversas vezes, porque a operação continua a produzir dados. Correções aplicadas manualmente ao arquivo de um ensaio se perdem no ensaio seguinte, e o mesmo defeito reaparece. Apenas a correção codificada é reaplicada quando o extrato é substituído.
3. Escopo: objetos de migração e critério de corte
Chama-se objeto de migração o conjunto de dados que constitui uma entidade do sistema de destino e que é carregado como unidade: material, parceiro de negócios, saldo de estoque, título em aberto. Cada objeto possui estrutura própria, campos obrigatórios próprios e dependências próprias.
Dados mestres e dados transacionais
Os dados mestres descrevem entidades estáveis: plano de contas, centros de custo, parceiro de negócios nos papéis de cliente e de fornecedor, material com seus dados gerais e por centro, listas técnicas e condições de preço. São migrados os registros ativos; cadastros sem movimento no período de corte são candidatos a descarte, com a decisão registrada e assinada pelo dono do dado.
Os dados transacionais descrevem posições e movimentos: saldos de estoque por material e centro, títulos em aberto a receber e a pagar, pedidos de compra e de venda pendentes, saldos contábeis de abertura. A prática consolidada consiste em migrar as posições em aberto; o histórico completo permanece consultável no sistema legado, mantido em modo de consulta, ou em repositório analítico.
Cada objeto acrescentado ao escopo acrescenta um ciclo completo de perfilagem, transformação, ensaio e reconciliação. A ampliação de escopo sem revisão de prazo é causa frequente de atraso no cutover, e a decisão de ampliá-lo cabe ao patrocinador do projeto.
Critério de corte
O critério de corte deve ser declarado por escrito e por objeto. Exemplos aplicáveis ao caso da distribuidora:
- Materiais com movimento nos últimos 24 meses ou com saldo de estoque diferente de zero.
- Clientes com pedido faturado nos últimos 36 meses ou com título em aberto.
- Títulos em aberto na data de corte, independentemente da data de emissão.
- Saldos de estoque na data de corte, conforme inventário confirmado.
O critério reduz volume e, sobretudo, reduz esforço de correção: não se investe trabalho de análise em cadastro que não será utilizado. A contrapartida é que o critério precisa ser decidido cedo, porque condiciona todas as etapas seguintes.
4. Ferramentas de carga do SAP
O ambiente SAP oferece mais de um caminho para inserção de dados em massa. A escolha condiciona o formato exigido do arquivo, mas não altera o trabalho de preparação, que é objeto desta aula.
| Recurso | Uso previsto | Entrada esperada |
|---|---|---|
| Migration Cockpit Migrate Your Data | Instrumento padrão de migração no S/4HANA, com objetos pré-configurados por área funcional e validações próprias. | Planilha no modelo publicado pelo próprio objeto, ou tabelas de preparação alimentadas por carga externa. |
| BAPI e interfaces de serviço | Cargas recorrentes, integração contínua com sistema externo, ou objetos sem cobertura pelo instrumento padrão. | Estrutura de parâmetros da interface, alimentada por programa. |
| IDoc | Troca assíncrona de documentos entre sistemas, com reprocessamento de mensagens com erro. | Mensagem estruturada em segmentos e campos definidos. |
| Entrada manual assistida | Volumes residuais e cadastros de exceção que não compensam automação. | Digitação com conferência dupla e registro de quem executou. |
O Migration Cockpit opera segundo dois modos de transferência, cuja disponibilidade depende da versão e do modo de implantação: transferência por arquivo, em que o modelo de planilha é preenchido e enviado, e transferência por tabelas de preparação, em que os dados são gravados em estruturas intermediárias do banco e lidos pelo instrumento. O primeiro modo é o adequado ao volume e ao contexto desta aula.
Confirmar com o parceiro, antes de definir o layout de saída, qual a versão do produto e qual o instrumento de carga homologado. O modelo de planilha varia entre versões e entre modos de implantação, e o layout obtido de outra fonte — apostila, repositório público, projeto anterior — costuma divergir do exigido pelo ambiente de destino.
Ferramentas de gerações anteriores, como o Legacy System Migration Workbench, ainda são encontradas em ambientes existentes, mas não constituem o caminho recomendado para novos projetos em S/4HANA. Quando surgirem em documentação do parceiro, devem ser tratadas como informação sobre o ambiente legado.
5. Anatomia do modelo de carga
O objeto de migração publica um modelo de planilha que é, simultaneamente, formulário e especificação. A leitura desse modelo antes da escrita do código evita retrabalho.
O que o modelo declara
- Uma aba por estrutura do objeto. O objeto material, por exemplo, separa dados gerais, textos descritivos, dados por centro e dados de unidade alternativa. Cada aba é uma tabela distinta, ligada às demais pela chave do objeto.
- Nome técnico do campo, além do rótulo em linguagem de negócio. É pelo nome técnico que o código deve se orientar, porque o rótulo varia com o idioma.
- Obrigatoriedade: campo-chave, obrigatório ou opcional. Campo-chave identifica o registro e não admite repetição na mesma aba.
- Tipo, comprimento máximo e formato esperado para cada campo.
As primeiras linhas do modelo contêm metadados — nome técnico, rótulo, indicação de obrigatoriedade — e o número da linha em que os dados efetivamente começam varia conforme a versão. Localize essa linha programaticamente, pela ocorrência dos nomes técnicos, em vez de fixar um número no código.
Para o caso da distribuidora, a estrutura simplificada adotada no notebook desta aula é a seguinte. Os nomes técnicos correspondem aos campos usuais do cadastro de material e de estoque e servem ao exercício; no projeto, devem ser substituídos pelos do modelo real do parceiro.
| Campo | Conteúdo | Regra | Observação |
|---|---|---|---|
MATNR | Código do material | Chave | Alinhado à direita com zeros quando puramente numérico. |
MAKTX | Descrição do material | Obrigatório | Comprimento máximo de 40 caracteres. |
MEINS | Unidade de medida básica | Obrigatório | Valor constante da tabela de unidades do ambiente. |
MATKL | Grupo de mercadorias | Obrigatório | Valor existente na configuração. |
WERKS | Centro | Chave por centro | Centro previamente existente no ambiente. |
BRGEW / NTGEW | Peso bruto e peso líquido | Opcional | Peso bruto não pode ser inferior ao líquido. |
LABST | Quantidade em estoque utilizável | Obrigatório no objeto de estoque | Valor numérico não negativo. |
6. Dimensões de qualidade de dados
Entende-se qualidade de dados como um conjunto de dimensões, cada uma correspondendo a uma verificação específica e, na maior parte dos casos, programável.
| Dimensão | Pergunta que responde | Verificação no caso da distribuidora |
|---|---|---|
| Completude | Os campos obrigatórios estão preenchidos? | Material sem unidade de medida básica. |
| Unicidade | Cada objeto do mundo real aparece uma única vez? | O mesmo rolamento cadastrado sob dois códigos. |
| Validade | O valor pertence ao domínio admitido pelo destino? | Grupo de mercadorias inexistente na configuração. |
| Consistência | Os valores concordam entre si e entre fontes? | Peso bruto inferior ao peso líquido. |
| Acurácia | O valor corresponde ao fato do mundo real? | Saldo divergente da contagem física do inventário. |
| Integridade referencial | As referências apontam para registros existentes? | Saldo de estoque em centro ainda não cadastrado. |
| Atualidade | O dado reflete a situação na data de corte? | Extrato gerado antes do último faturamento do dia. |
Completude, unicidade, validade, consistência, integridade referencial e atualidade são decidíveis sobre a própria base, por código. A acurácia não é: verificar se o saldo registrado corresponde ao estoque efetivamente existente exige confronto com o mundo — inventário físico, confirmação com o cliente, extrato bancário. Essa distinção é operacionalmente relevante, porque delimita o que a equipe de dados pode resolver sozinha e o que exige mobilização da operação.
Cada dimensão vira uma coluna do relatório de perfilagem e, depois, uma função de validação no notebook. A lista de dimensões constitui o roteiro de verificação.
7. Perfilagem da base legada
Perfilagem é a medição sistemática da base antes de qualquer correção. Seu produto é quantitativo, na forma "417 dos 3.184 materiais não têm unidade de medida; 62 descrições excedem 40 caracteres; 19 códigos aparecem mais de uma vez", em lugar de juízos genéricos como "o cadastro está ruim".
O que medir
- Por coluna: contagem de vazios, contagem de valores distintos, distribuição dos valores mais frequentes, comprimento mínimo e máximo.
- Por padrão: conformidade a expressão regular esperada, presença de caracteres de controle e de espaços duplicados, mistura de tipos na mesma coluna, sentinelas como
0,-1,N/Aea definir. - Por registro: duplicidade pela chave natural, duplicidade aproximada por descrição normalizada, registros órfãos em relação a outra aba, registros fora do critério de corte.
A perfilagem antecede a limpeza. Transformações escritas antes da medição tendem a tratar defeitos presumidos, a omitir os efetivamente presentes e a criar a falsa impressão de que a base foi tratada.
A tabela de perfil é entregue também ao dono do dado. Ela converte a discussão sobre qualidade em discussão sobre números e produz a lista de decisões que a área de negócio precisa tomar: o que fazer com os 417 materiais sem unidade, quais dos 19 códigos duplicados permanecem, se as 62 descrições longas são abreviadas por regra ou revisadas uma a uma.
8. Conformidade técnica dos campos
Antes de qualquer avaliação de negócio, o arquivo precisa satisfazer regras de formato. A rejeição por formato é a mais comum no primeiro ensaio e a mais barata de evitar.
Códigos com zeros à esquerda
Campos de código de comprimento fixo são armazenados com alinhamento à direita e preenchimento com zeros quando o valor é puramente numérico; valores que contêm letras permanecem alinhados à esquerda. O material 100234, em um campo de 18 posições, é armazenado como 000000000000100234, ao passo que ROL-6205 permanece inalterado.
O defeito surge no extrato: ao abrir a planilha, o aplicativo interpreta o código como número e descarta os zeros iniciais. A perda é irreversível a partir do arquivo, porque não há como distinguir, depois, entre um código que tinha zeros e outro que não tinha. Daí a exigência de ler toda a planilha como texto.
Datas
Campos de data são armazenados no formato ano-mês-dia sem separadores. O extrato costuma trazer três representações simultâneas na mesma coluna: texto no formato brasileiro 12/03/2019, texto ambíguo 03/12/2019 e número serial da planilha. A conversão exige declarar o formato de origem explicitamente, e nunca depender de inferência automática.
Números decimais
Extratos brasileiros trazem milhar com ponto e decimal com vírgula. O valor 1.234,50 convertido por inferência resulta em 1.23450 ou em erro, conforme a biblioteca. A conversão deve ser explícita e o resultado deve ser verificado por somatório antes e depois.
Unidades de medida
A unidade deve corresponder a um código existente na tabela de unidades do ambiente, no idioma de manutenção. Um mesmo conceito costuma aparecer no legado sob várias grafias — UN, un, PÇ, peça, PC —, todas designando unidade discreta. A padronização exige tabela de correspondência, tratada na seção seguinte.
Comprimento e caixa
Campos de texto possuem comprimento máximo, e campos de chave são convertidos para maiúsculas. Descrições excedentes exigem decisão do responsável pelo dado; o corte automático é vedado pela RM-03.
A justificativa é de negócio: a descrição do material é o que o comprador e o almoxarife leem para identificar a peça. Um corte automático em 40 caracteres pode eliminar exatamente a medida que distingue duas peças semelhantes, e o efeito só aparece meses depois, na forma de compra errada.
9. Domínios e mapeamento de-para
Campos de domínio admitem apenas valores previamente configurados no ambiente de destino: unidade de medida, grupo de mercadorias, tipo de material, condição de pagamento, centro, depósito. O valor do legado raramente coincide com o do destino, e a correspondência é decisão de negócio.
Estrutura da tabela de-para
- Valor de origem, exatamente como consta no extrato, incluindo variações de caixa e espaços.
- Valor de destino, existente na configuração do ambiente.
- Responsável pela decisão e data, porque a correspondência é decisão revisável.
- Tratamento do valor não mapeado: rejeitar o registro ou atribuir valor padrão declarado.
| Origem | Destino | Observação |
|---|---|---|
UN, un, PÇ, peça, PC | PC | Unidade discreta. |
KG, Kg, quilo | KG | Massa. |
M, metro, mt | M | Comprimento. |
CX12 | CX | Exige fator de conversão declarado como unidade alternativa. |
vazio, —, N/A | rejeitar | Não há valor padrão admissível para unidade básica. |
Atribuir valor padrão a todo registro cujo valor de origem não esteja mapeado, para eliminar a rejeição. O procedimento produz um arquivo que carrega integralmente e uma base que descreve incorretamente a operação. A rejeição tem por finalidade provocar a decisão do responsável pelo dado.
O mapeamento é dado de projeto: vive em arquivo próprio, versionado junto ao código, e é revisado pela área de negócio. Constantes de correspondência embutidas no meio do notebook não são auditáveis nem reutilizáveis entre ensaios.
10. Duplicidade e consolidação
Duplicidade é a presença de mais de um registro para o mesmo objeto do mundo real. É comum em bases legadas mantidas por vários usuários ao longo de anos, e sua correção envolve decisão de negócio.
Detecção
- Exata: coincidência da chave natural após normalização — código do material, documento fiscal do cliente, par material e centro.
- Aproximada: descrições equivalentes após remoção de acentos, pontuação e espaços redundantes, ou comparação por similaridade textual. O resultado consiste em candidatos a duplicidade, sujeitos a confirmação.
Consolidação
Escolhido o registro que permanece, o critério deve ser declarado: maior completude de campos, movimentação mais recente, ou decisão explícita do dono do dado. O que se preserva depois é tão importante quanto a escolha: a tabela de correspondência entre o identificador descartado e o mantido é artefato permanente do projeto, porque documentos históricos, integrações e relatórios do legado continuam a referenciar o código antigo.
11. Dependência e sequência de carga
Objetos de migração possuem dependências, e a ordem de carga decorre de restrição de integridade. Não é possível carregar saldo de estoque de um material que ainda não existe, nem título de um cliente ainda não criado.
centros, depósitos, grupos] --> CTA[Plano de contas
centros de custo] CFG --> BP[Parceiro de negócios
cliente e fornecedor] CFG --> MAT[Material
dados gerais e por centro] MAT --> EST[Saldos
de estoque] BP --> TIT[Títulos
em aberto] CTA --> TIT MAT --> PED[Pedidos
em aberto] BP --> PED
Duas consequências práticas decorrem do grafo de dependências.
- Para a validação: a verificação de integridade referencial de um objeto deve usar a lista de chaves efetivamente carregadas do objeto antecedente, e não a lista prevista. Apenas a primeira reflete o que existe no ambiente; a segunda inclui registros que podem ter sido rejeitados.
- Para o cronograma: a falha na carga de um objeto antecedente bloqueia todos os dependentes. O ensaio percorre a sequência completa justamente para expor esse encadeamento antes da janela de cutover, quando não há tempo de reagir.
12. O notebook: ambiente e base do caso
O trabalho prático é realizado no Google Colab, que já disponibiliza pandas e openpyxl sem instalação. Cada célula desta seção e das seguintes compõe um notebook único, executável na ordem apresentada.
Acesse colab.research.google.com, crie um notebook, renomeie-o para carga-sap-<objeto>-<grupo>.ipynb e conceda acesso de leitura ao professor. Execute a célula de verificação abaixo antes de prosseguir.
import pandas as pd
import openpyxl, sys
print('python ', sys.version.split()[0])
print('pandas ', pd.__version__)
print('openpyxl', openpyxl.__version__)
A base do caso
Para que o notebook seja executável por todos os grupos desde o início da aula, a célula seguinte gera o extrato legado da distribuidora. A base é sintética e contém, propositalmente, os defeitos discutidos nas seções 6 a 10: códigos sem zeros à esquerda, descrição excedente, unidades com grafias divergentes, grupo de mercadorias fora do domínio, centro inexistente, decimais no padrão brasileiro, datas em formatos misturados, duplicidade e um saldo de estoque órfão.
import pandas as pd
materiais = pd.DataFrame([
# codigo descricao unidade grupo centro peso_bruto peso_liquido dt_criacao
['100234', 'Rolamento rigido de esferas 6205 2RS', 'UN', 'ROLAM', '1000', '0,132', '0,128', '12/03/2019'],
['100235', 'Rolamento rigido de esferas 6206 ZZ', 'un', 'ROLAM', '1000', '0,198', '0,190', '12/03/2019'],
['100236', 'Correia dentada perfil HTD 8M largura 30mm passo 8mm reforcada', 'PC', 'CORRE', '1000', '0,450', '0,440', '05/07/2020'],
['100237', 'Graxa multiuso litio EP2 balde 20kg', 'KG', 'LUBRI', '1000', '20,500', '20,000', '18/11/2021'],
['100238', 'Retentor 35x52x7 nitrilica', 'PC', 'RETEN', '2000', '0,015', '0,014', '02/02/2022'],
['100239', 'Mangueira hidraulica 1/2 pol R2AT', 'M', 'MANGU', '2000', '1,100', '1,050', '30/09/2022'],
['100240', 'Parafuso sextavado M10x50 classe 8.8 zincado', 'PÇ', 'FIXAD', '1000', '0,048', '0,046', '14/01/2023'],
['100241', 'Oleo hidraulico ISO VG 68 tambor 200L', 'peça','LUBRI', '3000', '180,000', '178,000', '2023-04-22'],
['100242', 'Sensor indutivo M12 PNP NA 4mm', '', 'SENSO', '2000', '0,090', '0,088', '11/08/2023'],
['100243', 'Valvula solenoide 3/2 vias 24VCC', 'PC', 'PNEUM', '1000', '0,320', '0,315', '27/05/2024'],
['100234', 'Rolamento rigido de esferas 6205 2RS', 'PC', 'ROLAM', '1000', '0,132', '0,128', '12/03/2019'],
['ROL-778','Rolamento conico 30206 importado', 'PC', 'XXXXX', '1000', '0,260', '0,255', '09/09/2021'],
], columns=['CODIGO','DESCRICAO','UNIDADE','GRUPO','CENTRO',
'PESO_BRUTO','PESO_LIQUIDO','DT_CRIACAO'])
estoque = pd.DataFrame([
['100234', '1000', '0001', '184,000'],
['100235', '1000', '0001', '96,000'],
['100236', '1000', '0001', '12,000'],
['100237', '1000', '0002', '7,500'],
['100238', '2000', '0001', '340,000'],
['100239', '2000', '0001', '58,250'],
['100240', '1000', '0001', '2.400,000'],
['100241', '3000', '0001', '3,000'],
['100243', '1000', '0001', '0,000'],
['100999', '1000', '0001', '15,000'], # material inexistente na aba Materiais
], columns=['CODIGO','CENTRO','DEPOSITO','QUANTIDADE'])
with pd.ExcelWriter('extrato_legado.xlsx', engine='openpyxl') as saida:
materiais.to_excel(saida, sheet_name='Materiais', index=False)
estoque.to_excel(saida, sheet_name='Estoque', index=False)
print('extrato_legado.xlsx gerado:', len(materiais), 'materiais,', len(estoque), 'saldos')
Substitua a célula 2 pelo carregamento do extrato real, mantendo inalterado todo o restante do notebook. Se o extrato ainda não estiver disponível, o grupo trabalha sobre esta base até obtê-lo; o código permanece o mesmo e apenas a origem dos dados é substituída.
13. Ingestão e perfilagem
A ingestão obedece a duas exigências que decorrem diretamente da seção 8: ler tudo como texto e não permitir conversões automáticas de valores ausentes.
# dtype=str preserva zeros à esquerda e evita que códigos virem números.
# keep_default_na=False mantém 'NA' e 'N/A' como texto, para que a
# perfilagem possa distinguir campo vazio de campo preenchido com sentinela.
abas = pd.read_excel('extrato_legado.xlsx', sheet_name=None,
dtype=str, keep_default_na=False)
for nome, df in abas.items():
print(f'{nome:<12} {df.shape[0]:>5} linhas {df.shape[1]:>3} colunas')
materiais = abas['Materiais'].copy()
estoque = abas['Estoque'].copy()
TOTAL_MATERIAIS = len(materiais) # guardado para a reconciliação
TOTAL_ESTOQUE = len(estoque)
def perfilar(df):
"""Uma linha por coluna: vazios, cardinalidade, comprimento e exemplo."""
linhas = []
for col in df.columns:
s = df[col].astype(str).str.strip()
nao_vazios = s[s != '']
linhas.append({
'campo': col,
'vazios': int((s == '').sum()),
'distintos': int(s.nunique()),
'compr_min': int(nao_vazios.str.len().min()) if len(nao_vazios) else 0,
'compr_max': int(nao_vazios.str.len().max()) if len(nao_vazios) else 0,
'exemplo': nao_vazios.iloc[0] if len(nao_vazios) else '',
})
return pd.DataFrame(linhas)
display(perfilar(materiais))
# distribuição dos valores de cada campo de domínio
for campo in ['UNIDADE', 'GRUPO', 'CENTRO']:
print(f'\n--- {campo} ---')
print(materiais[campo].str.strip().value_counts(dropna=False))
# duplicidade pela chave natural
dups = materiais[materiais.duplicated(subset=['CODIGO'], keep=False)]
print('\ncódigos duplicados:', dups['CODIGO'].nunique(),
'| linhas envolvidas:', len(dups))
display(dups)
# integridade referencial entre as duas abas
orfaos = estoque[~estoque['CODIGO'].isin(materiais['CODIGO'])]
print('\nsaldos sem material correspondente:', len(orfaos))
display(orfaos)
A perfilagem da base do caso deve expor: cinco grafias para a mesma unidade discreta (UN, un, PC, PÇ, peça) e uma unidade vazia; um grupo de mercadorias fora do domínio; um centro inexistente no destino; um código repetido em duas linhas; duas descrições acima de 40 caracteres; e um saldo de estoque sem material correspondente.
Cada ocorrência deve ser confirmada antes da escrita de qualquer transformação. O mesmo procedimento de confirmação aplica-se ao projeto do parceiro.
14. Transformações canônicas
Cada regra de transformação é escrita como função que recebe um valor e devolve um valor, sem depender do DataFrame. A separação permite verificar a regra com poucos casos — inclusive os de fronteira — antes de aplicá-la a milhares de registros.
import re
import unicodedata
def codigo_com_zeros(valor, tamanho=18):
"""Numérico: alinha à direita com zeros. Alfanumérico: mantém como está."""
v = (valor or '').strip().upper()
return v.zfill(tamanho) if v.isdigit() else v
def texto_limpo(valor):
"""Remove espaços redundantes e caracteres de controle. Não trunca."""
v = unicodedata.normalize('NFC', valor or '')
v = re.sub(r'\s+', ' ', v)
return ''.join(c for c in v if unicodedata.category(c)[0] != 'C').strip()
def decimal_br(valor):
"""'1.234,50' -> 1234.5 ; valor inválido ou vazio -> None"""
v = (valor or '').strip().replace('.', '').replace(',', '.')
return float(v) if re.fullmatch(r'-?\d+(\.\d+)?', v) else None
def data_iso(valor):
"""Aceita dd/mm/aaaa e aaaa-mm-dd; devolve aaaammdd ou None."""
v = (valor or '').strip()
for formato in ('%d/%m/%Y', '%Y-%m-%d'):
try:
return pd.to_datetime(v, format=formato).strftime('%Y%m%d')
except (ValueError, TypeError):
continue
return None
# verificação das regras antes da aplicação em massa
assert codigo_com_zeros('100234') == '000000000000100234'
assert codigo_com_zeros('ROL-778') == 'ROL-778'
assert texto_limpo('Rolamento rigido de esferas') == 'Rolamento rigido de esferas'
assert decimal_br('2.400,000') == 2400.0
assert decimal_br('n/d') is None
assert data_iso('12/03/2019') == '20190312'
assert data_iso('2023-04-22') == '20230422'
print('regras verificadas')
# Tabelas de-para: dado de projeto, revisado pela área de negócio.
DEPARA_UNIDADE = {
'UN': 'PC', 'PÇ': 'PC', 'PC': 'PC', 'PEÇA': 'PC', 'PECA': 'PC',
'KG': 'KG', 'QUILO': 'KG',
'M': 'M', 'METRO': 'M', 'MT': 'M',
}
GRUPOS_CONFIGURADOS = {'ROLAM', 'CORRE', 'LUBRI', 'RETEN',
'MANGU', 'FIXAD', 'SENSO', 'PNEUM'}
CENTROS_CARREGADOS = {'1000', '2000'} # obtidos do ambiente de destino
def unidade_destino(valor):
return DEPARA_UNIDADE.get(texto_limpo(valor).upper()) # None se não mapeado
materiais['MATNR'] = materiais['CODIGO'].map(codigo_com_zeros)
materiais['MAKTX'] = materiais['DESCRICAO'].map(texto_limpo)
materiais['MEINS'] = materiais['UNIDADE'].map(unidade_destino)
materiais['MATKL'] = materiais['GRUPO'].map(lambda v: texto_limpo(v).upper())
materiais['WERKS'] = materiais['CENTRO'].map(lambda v: texto_limpo(v))
materiais['BRGEW'] = materiais['PESO_BRUTO'].map(decimal_br)
materiais['NTGEW'] = materiais['PESO_LIQUIDO'].map(decimal_br)
materiais['ERSDA'] = materiais['DT_CRIACAO'].map(data_iso)
display(materiais[['CODIGO','MATNR','MAKTX','MEINS','MATKL','WERKS','BRGEW','ERSDA']])
A função unidade_destino devolve None quando o valor de origem não consta da tabela, em vez de atribuir um padrão. A ausência é detectada na validação e produz rejeição explícita, que é o comportamento exigido pela seção 9.
15. Validação e quarentena
A validação percorre cada registro e acumula os motivos de rejeição. O registro reprovado é separado em quarentena, com o motivo em linguagem compreensível por quem não escreveu o código, porque quem decide sobre ele é o dono do dado.
Quando uma função de transformação devolve None, o valor é convertido em NaN ao ser atribuído a uma coluna numérica do DataFrame. E NaN é verdadeiro em contexto booleano: a verificação if not valor não detecta a ausência, e if valor is None tampouco, porque o objeto já não é None.
O efeito é silencioso e grave: o registro sem unidade de medida passa pela validação, entra no arquivo de carga e é rejeitado apenas pelo sistema de destino ou, em situação mais grave, aceito com valor indevido. Por isso a validação abaixo começa por uma função ausente, que trata os três casos: None, NaN e texto vazio.
LIMITE_DESCRICAO = 40
def ausente(valor):
"""True para None, NaN e texto vazio.
Necessária porque o None devolvido pelas funções de transformação é
convertido em NaN ao entrar na coluna, e NaN é VERDADEIRO em contexto
booleano: 'if not valor' não detecta a ausência. Ver seção 15.
"""
return pd.isna(valor) or str(valor).strip() == ''
def validar_material(linha):
erros = []
if ausente(linha['MATNR']):
erros.append('código ausente')
if ausente(linha['MAKTX']):
erros.append('descrição ausente')
elif len(linha['MAKTX']) > LIMITE_DESCRICAO:
erros.append(f'descrição com {len(linha["MAKTX"])} caracteres '
f'(limite {LIMITE_DESCRICAO})')
if ausente(linha['MEINS']):
erros.append(f'unidade "{linha["UNIDADE"]}" fora do de-para')
if linha['MATKL'] not in GRUPOS_CONFIGURADOS:
erros.append(f'grupo "{linha["MATKL"]}" inexistente na configuração')
if linha['WERKS'] not in CENTROS_CARREGADOS:
erros.append(f'centro "{linha["WERKS"]}" não carregado no destino')
if ausente(linha['BRGEW']):
erros.append('peso bruto inválido ou ausente')
elif not ausente(linha['NTGEW']) and linha['BRGEW'] < linha['NTGEW']:
erros.append('peso bruto inferior ao peso líquido')
if ausente(linha['ERSDA']):
erros.append('data de criação em formato não reconhecido')
return '; '.join(erros)
materiais['erros'] = materiais.apply(validar_material, axis=1)
# duplicidade é propriedade do conjunto, não da linha: avaliada à parte
duplicados = materiais.duplicated(subset=['MATNR'], keep=False)
materiais.loc[duplicados, 'erros'] = materiais.loc[duplicados, 'erros'].apply(
lambda e: '; '.join(filter(None, [e, 'código duplicado no extrato']))
)
aprovados = materiais[materiais['erros'] == ''].copy()
quarentena = materiais[materiais['erros'] != ''].copy()
print(len(aprovados), 'aprovados |', len(quarentena), 'em quarentena')
assert len(aprovados) + len(quarentena) == TOTAL_MATERIAIS
display(quarentena[['CODIGO','DESCRICAO','erros']])
A verificação é feita pela asserção da célula 8. Sem ela, uma operação de junção mal formulada ou um filtro indevido pode eliminar registros sem qualquer sinal, e a perda seria detectada apenas na reconciliação ou permaneceria não detectada.
# A integridade referencial usa as chaves EFETIVAMENTE aprovadas,
# não a lista original de materiais (ver seção 11).
MATERIAIS_CARREGADOS = set(aprovados['MATNR'])
estoque['MATNR'] = estoque['CODIGO'].map(codigo_com_zeros)
estoque['WERKS'] = estoque['CENTRO'].map(lambda v: texto_limpo(v))
estoque['LGORT'] = estoque['DEPOSITO'].map(lambda v: texto_limpo(v))
estoque['LABST'] = estoque['QUANTIDADE'].map(decimal_br)
def validar_estoque(linha):
erros = []
if linha['MATNR'] not in MATERIAIS_CARREGADOS:
erros.append('material não carregado no destino')
if linha['WERKS'] not in CENTROS_CARREGADOS:
erros.append(f'centro "{linha["WERKS"]}" não carregado no destino')
if ausente(linha['LABST']):
erros.append('quantidade inválida ou ausente')
elif linha['LABST'] < 0:
erros.append('quantidade negativa')
return '; '.join(erros)
estoque['erros'] = estoque.apply(validar_estoque, axis=1)
estoque_ok = estoque[estoque['erros'] == ''].copy()
estoque_qtn = estoque[estoque['erros'] != ''].copy()
print(len(estoque_ok), 'saldos aprovados |', len(estoque_qtn), 'em quarentena')
assert len(estoque_ok) + len(estoque_qtn) == TOTAL_ESTOQUE
display(estoque_qtn[['CODIGO','CENTRO','QUANTIDADE','erros']])
Materiais rejeitados na célula 8 provocam a rejeição dos saldos correspondentes na célula 9. O encadeamento é exatamente o do grafo de dependências da seção 11, e é essa propagação que o ensaio de carga precisa expor antes do cutover.
16. Emissão do arquivo de carga
O arquivo de carga é escrito sobre o modelo publicado pelo objeto de migração, preservando o cabeçalho. A localização da linha de dados é feita por busca dos nomes técnicos, conforme a seção 5.
from openpyxl import load_workbook
def escrever_no_modelo(caminho_modelo, aba, df, campo_ancora, destino):
"""Escreve df sob o cabeçalho técnico do modelo, sem alterá-lo."""
wb = load_workbook(caminho_modelo)
ws = wb[aba]
colunas, primeira_linha = None, None
for linha in ws.iter_rows(min_row=1, max_row=12):
valores = [(c.value or '') for c in linha]
if campo_ancora in valores:
colunas = {v: i for i, v in enumerate(valores, start=1) if v}
primeira_linha = linha[0].row + 1
break
if colunas is None:
raise ValueError(f'cabeçalho com {campo_ancora} não encontrado em {aba}')
for deslocamento, (_, reg) in enumerate(df.iterrows()):
for campo, coluna in colunas.items():
if campo in reg.index:
ws.cell(row=primeira_linha + deslocamento,
column=coluna, value=reg[campo])
wb.save(destino)
print(destino, '→', len(df), 'registros a partir da linha', primeira_linha)
Quando o modelo oficial do parceiro ainda não estiver disponível, o exercício emite a planilha diretamente, com os nomes técnicos na primeira linha. A substituição posterior pelo modelo real afeta apenas esta célula.
ENSAIO = 'ensaio1'
CAMPOS_MATERIAL = ['MATNR','MAKTX','MEINS','MATKL','WERKS','BRGEW','NTGEW','ERSDA']
CAMPOS_ESTOQUE = ['MATNR','WERKS','LGORT','LABST']
with pd.ExcelWriter(f'carga_material_{ENSAIO}.xlsx', engine='openpyxl') as saida:
aprovados[CAMPOS_MATERIAL].to_excel(saida, sheet_name='Basic Data', index=False)
estoque_ok[CAMPOS_ESTOQUE].to_excel(saida, sheet_name='Stock', index=False)
with pd.ExcelWriter(f'excecoes_{ENSAIO}.xlsx', engine='openpyxl') as saida:
quarentena[['CODIGO','DESCRICAO','UNIDADE','GRUPO','CENTRO','erros']] \
.to_excel(saida, sheet_name='Materiais', index=False)
estoque_qtn[['CODIGO','CENTRO','DEPOSITO','QUANTIDADE','erros']] \
.to_excel(saida, sheet_name='Estoque', index=False)
print('arquivos do', ENSAIO, 'gerados')
O nome de cada arquivo identifica o objeto e o ensaio. Os arquivos de ensaios anteriores são preservados: a comparação entre eles demonstra a evolução da qualidade da base e é a evidência que sustenta a decisão de autorizar o cutover.
17. Reconciliação e totais de controle
A reconciliação confronta origem e destino por meio de totais de controle. Sem ela, pode-se afirmar apenas que o arquivo foi aceito, afirmação que não atesta a correção dos dados.
Totais que se confrontam
- Contagem de registros por objeto e por agrupamento relevante — centro, grupo de mercadorias, segmento de cliente.
- Somatório dos campos quantitativos: quantidade em estoque, valor de títulos em aberto, saldo contábil.
- Contagem de valores distintos em campos-chave.
- Registros em quarentena, agrupados por motivo.
origem = estoque.assign(QTD=estoque['QUANTIDADE'].map(decimal_br)) \
.groupby('CENTRO')['QTD'].sum()
destino = estoque_ok.groupby('WERKS')['LABST'].sum()
conf = pd.concat([origem, destino], axis=1,
keys=['origem', 'destino']).fillna(0)
conf['diferenca'] = conf['destino'] - conf['origem']
display(conf)
print('\ndivergências:')
display(conf[conf['diferenca'] != 0])
print('\nquarentena por motivo:')
display(quarentena['erros'].str.split('; ').explode().value_counts())
Na base do caso, a reconciliação fecha no centro 2000 e apresenta divergência nos centros 1000 e 3000. Ambas são integralmente explicadas pela quarentena: no centro 1000, pelos saldos dos materiais rejeitados por descrição excedente ou por duplicidade, e pelo saldo sem material correspondente; no centro 3000, pelo centro inexistente no destino. Essa correspondência, em que cada unidade de diferença é atribuída a um registro identificado, é o que se apresenta ao dono do dado para obter o aceite.
Quanto à magnitude, cinco saldos rejeitados retiram do destino 2.614 unidades, das quais 2.400 correspondem a um único item de alto giro. A taxa de rejeição medida em registros não é proporcional ao impacto medido em quantidade, e ambas as medidas devem constar do relatório apresentado à área de negócio.
Diferença de poucas unidades em um somatório costuma indicar registro perdido em uma junção, e não erro de arredondamento. A investigação é obrigatória: o mesmo defeito que produz diferença pequena em ensaio produz diferença grande com o volume completo do cutover.
18. Ensaios de carga e cutover
A carga definitiva constitui a repetição de um procedimento já executado e cronometrado. Os ensaios existem para que a primeira execução do procedimento não coincida com a janela em que a operação está parada.
| Ensaio | Propósito | Critério de saída |
|---|---|---|
| Primeiro | Validar o formato do arquivo e expor os defeitos estruturais da base. | Arquivo aceito pelo instrumento de carga; perfil de erros quantificado. |
| Segundo | Verificar a eficácia das correções e a integridade entre objetos dependentes. | Taxa de rejeição dentro do limite acordado; reconciliação fechada nos objetos principais. |
| Final | Reproduzir integralmente o procedimento e medir a duração de cada etapa. | Janela de execução compatível com a acordada; plano de retorno testado. |
| Cutover | Executar a carga com o extrato da data de corte. | Reconciliação assinada pelo dono do dado; autorização de entrada em operação. |
A partir do segundo ensaio, mede-se também a duração de cada etapa. A janela de cutover é finita e frequentemente noturna ou de fim de semana; etapa cuja duração não foi medida é risco não dimensionado. O plano de retorno — a decisão sobre o que fazer se a carga falhar no meio — integra o ensaio final e é definido antes da janela de cutover.
Carga interrompida precisa ser retomada sem duplicar o que já entrou. Verifique, antes de reexecutar, quais chaves já existem no destino e carregue apenas o complemento. A reexecução sem essa verificação é causa frequente de registros duplicados em produção.
19. Governança e antipadrões
Responsabilidade sobre o dado
- Cada objeto de migração possui um responsável identificado na área de negócio, que decide sobre exceções e assina a reconciliação. A equipe técnica executa; não decide o conteúdo.
- As decisões de descarte, consolidação e valor padrão são registradas com data e autor.
- O código de transformação e as tabelas de-para são versionados no repositório do projeto, além de mantidos no ambiente de notebook.
Dados pessoais
Extratos de clientes, fornecedores e empregados contêm dados pessoais, e a proteção exigida pela Lei Geral de Proteção de Dados Pessoais alcança, além do sistema, as planilhas intermediárias, os notebooks e as pastas em que os arquivos são armazenados durante o projeto.
- O tratamento exige base legal e finalidade declarada, também na fase de migração.
- Ambientes de ensaio devem utilizar dados descaracterizados sempre que o teste não exigir o valor real.
- A retenção dos extratos intermediários é limitada e declarada, com eliminação ao final do projeto.
- O acesso aos arquivos é restrito aos integrantes da equipe de migração.
Correção manual no arquivo de carga, sem alterar o código que o gera: o defeito reaparece no ensaio seguinte.
Valor padrão para eliminar rejeição: produz arquivo que carrega e base que descreve a operação de forma incorreta.
Migração do histórico completo por ausência de critério de corte: multiplica o esforço de correção sobre dados que não serão utilizados.
Carga declarada concluída sem reconciliação: atesta-se apenas a aceitação do arquivo, sem demonstração da correção dos dados.
Extrato com dados pessoais em pasta pessoal, sem controle de acesso nem prazo de eliminação.
20. Atividade e critérios de aceitação
A atividade é conduzida em aula e concluída no bloco de desenvolvimento do projeto. Seu produto é insumo direto da aula de testes e cutover no contexto de sistemas empresariais.
Roteiro
- Escolher um objeto de migração pertinente ao caso do parceiro — material, cliente, fornecedor, saldo de estoque ou título em aberto.
- Obter o extrato legado em planilha, com pelo menos duas abas relacionadas. Na indisponibilidade do extrato real, utilizar a base sintética da seção 12.
- Ingerir com
dtype=stre produzir a tabela de perfilagem, incluindo campos de domínio, duplicidade e registros órfãos. - Declarar as regras de transformação, uma função por regra, com verificação por asserção.
- Declarar o mapeamento de-para de pelo menos um campo de domínio, com o tratamento previsto para o valor não mapeado.
- Validar registro a registro, separar a quarentena e emitir o arquivo de carga.
- Apresentar a reconciliação de contagens e somatórios, com cada divergência atribuída a registros identificados.
Critérios de aceitação do produto
- O notebook executa do início ao fim, sem intervenção manual, a partir do extrato original.
- A soma de aprovados e quarentena iguala o total lido, verificada por asserção no próprio notebook.
- Todo registro em quarentena possui motivo legível por quem não escreveu o código.
- Nenhum valor de domínio é atribuído por omissão; valor não mapeado produz rejeição explícita.
- Nenhum valor é truncado silenciosamente.
- A reconciliação é apresentada como tabela, com as divergências explicadas por registros identificados.
- Os arquivos de saída identificam o objeto e o ensaio no nome.
Notebook no Google Colab com acesso concedido ao professor, acompanhado do arquivo de carga e do relatório de exceções gerados. O notebook é o artefato avaliado: arquivos produzidos sem o código que os gera não atendem aos critérios.
Dos registros encaminhados à quarentena pelo grupo, quantos decorrem de defeito de formato, corrigível por código, e quantos exigem decisão de negócio? A proporção entre os dois indica quanto do prazo de migração depende da disponibilidade do dono do dado, e não da equipe técnica.
21. Referências
- SAP — Documentação do SAP S/4HANA, seção de migração de dados e do Migration Cockpit.
- DAMA International — DAMA-DMBOK: Data Management Body of Knowledge. 2. ed. Technics Publications, 2017. Capítulos de qualidade de dados e de integração e interoperabilidade.
- ISO/IEC 25012 — Data Quality Model. Dimensões de qualidade de dados.
- McKinney, W. — Python for Data Analysis. 3. ed. O'Reilly, 2022.
- Documentação do pandas —
read_excel,ExcelWriter, tratamento de valores ausentes. - Documentação do openpyxl — leitura e escrita em planilhas preservando formatação.
- Brasil — Lei nº 13.709/2018, Lei Geral de Proteção de Dados Pessoais.
- Redman, T. — Data Driven: Profiting from Your Most Important Business Asset. Harvard Business Review Press, 2008.