- plano-de-ataque: skill de projeto, especifica deste repo (arquivos, formato da planilha, coordenadas de pixel da planta, cores) - pptx-planta-progresso: skill generica/reutilizavel com a metodologia (medicao de eixos em pixel, correcao de crop srcRect, duplicacao de slide em python-pptx, fidelidade de cor, empacotamento sem dependencias para Linux/Windows, validacao sem Office instalado) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
8.7 KiB
Plano de Ataque — PPTX de progresso por quinzena
Este skill resume o que foi aprendido construindo gerar_plano.py: um gerador
que pega um PPTX-modelo (1 slide de "uma quinzena") e uma planilha de
planejamento, e produz um PPTX com 1 slide por quinzena, pintando cada
quadrante da planta conforme o status de cada estrutura naquela data.
Onde as coisas estão
gerar_plano.py(raiz) — script-fonte, único arquivo, sem módulos próprios além deopenpyxl,python-pptx,lxml.cores.json(raiz) — cores/opacidades/parâmetros de quinzena, editável sem tocar no código (ver seção "Cores" abaixo).dist-linux/— pacote pronto (executável standalone via PyInstaller) + modelo.pptx+ planilha.xlsxde exemplo +LEIA-ME.txt.dist-windows/— pacote pronto (Python "embeddable" do python.org com as libs já instaladas empython/Lib/site-packages, sem precisar instalar nada) +PlanoDeAtaque.bat+ mesmos exemplos +LEIA-ME.txt+ um.zipcom tudo empacotado.
Fluxo normal de uso (regenerar o PPTX)
- Reunir em uma pasta: 1
.jsonde cores, 1.pptxmodelo (slide 2 = layout de uma quinzena), 1.xlsxde planejamento. python3 gerar_plano.pydentro dessa pasta (ou rodar o executável/.batemdist-linux/dist-windows).- O script AUTO-DETECTA os 3 arquivos pela extensão na própria pasta (não
usa nomes fixos) e gera
<nome-do-modelo>_ATUALIZADO.pptx. - Se o usuário mandar uma planilha/modelo NOVO por upload, é só copiar para
.uploads/(ou onde estiver rodando), gerar, e comparar visualmente (ver "Como validar" abaixo) antes de entregar.
Modelo de dados da planilha
Aba "Planejamento", dados a partir da linha 4:
- Coluna B: nome do quadrante, formato
"<col1>-<col2>/<linha1>-<linha2>", ex."10-11/A1-B"(colunas são números 1-48; linhas são rótulos entreD, C, B1, B, A1, A). Regex usado:^(\d+)-(\d+)/([A-Z0-9]+)-([A-Z0-9]+)$. - Colunas C/D: Geotecnia (Início/Término)
- Colunas E/F: Infraestrutura (Início/Término)
- Colunas G/H: Superestrutura (Início/Término)
Status por quadrante/estrutura numa data de corte p_end:
None(não pintar) se Início vazio ou Início > p_end"concluido"se Término preenchido e Término <= p_end"andamento"caso contrário
Quinzenas
Períodos de 14 dias começando em 2026-12-14 = Quinzena 02 (a planilha
não tem atividade antes dessa data — por isso a numeração já começa em 02,
não em 01). Esses três parâmetros ficam em cores.json → quinzena e dão
para editar sem mexer no código: duracao_dias, data_inicio,
numero_inicial.
Cores (cores.json)
Uma seção por estrutura (geotecnia, superestrutura, infraestrutura),
cada uma com andamento e concluido, cada um com preenchimento (hex sem
#), borda (hex sem #) e alpha_preenchimento (0-100). A legenda do
slide sempre usa 100% de opacidade (fiel à cor pedida); o desenho usa o
alpha_preenchimento configurado — mais baixo na geotecnia (cobre o
quadrante inteiro, não pode esconder a planta) e mais alto nos marcadores de
pilar/bloco (são pequenos, não atrapalham).
Regra de ouro ao pedir "mude a cor X pra Y": sempre pedir/usar um print em ALTA resolução da legenda e fazer amostragem de pixel (não confiar em screenshot comprimido/pequeno — já erramos assim uma vez nesta conversa). Amostrar tanto o preenchimento quanto a BORDA (são cores diferentes, e a legenda de referência sempre tem as duas).
Como as camadas são desenhadas (importante para não perder fidelidade)
- Geotecnia/Estaqueamento: retângulo arredondado cobrindo o quadrante INTEIRO (não um recorte menor).
- Superestrutura/Pilar e Infraestrutura/Bloco: NÃO são um retângulo
proporcional ao quadrante — são marcadores pequenos (quadrado arredondado,
marcador_fracaodo espaçamento entre colunas) plantados nos 4 VÉRTICES (colunaxlinha) de cada quadrante ativo. Como vértices são compartilhados entre quadrantes vizinhos, dedupe por ponto usando o status mais avançado (concluido>andamento) para não desenhar marcadores duplicados/ sobrepostos no mesmo lugar.
Mapeamento pixel → EMU (a parte mais delicada)
As posições de colunas/linhas da planta (IM1_COLPX, IM2_COLPX,
IM1_ROWPX, IM2_ROWPX no script) são constantes medidas a dedo nas imagens
do modelo, usando detecção de blobs por soma de pixels escuros por
coluna/linha (círculos dos eixos, ver histórico da conversa para o script de
medição). Se a planta do modelo mudar, essas constantes têm que ser
remedidas — sempre medir direto na imagem que está DE FATO embutida no
.pptx (ppt/media/imageN.png dentro do zip), não em uma cópia enviada
separadamente por upload: já tivemos um bug de desalinhamento porque as duas
não eram pixel-idênticas.
Gotcha crítico: se a imagem no slide tiver um corte aplicado no
PowerPoint (<a:srcRect t="..." b="..." l="..." r=".../> dentro do
<p:blipFill>), a conversão pixel→EMU tem que descontar esse corte, senão
tudo fica deslocado (geralmente para baixo/lado). Sempre checar o XML do
<p:pic> de cada imagem do slide-modelo antes de assumir que pixel 0 =
canto da imagem exibida. Foi exatamente isso que causou o "a imagem de baixo
está deslocada" nesta conversa.
Duplicar slides em python-pptx
Não existe API pública para duplicar slide. A receita usada:
dest = prs.slides.add_slide(source.slide_layout), depois remover os placeholders que a layout injeta.- Para cada relationship de imagem do slide-fonte,
dest.part.relate_to(...)para criar a relação equivalente no slide-destino (isso gera um rId novo, que pode não bater com o rId original). - Deep-copy de cada shape do slide-fonte e reescrever os atributos
r:embed/r:linkdiretamente no elemento XML (el.set(qn('r:embed'), novo_rid)), nunca com substituição de texto/regex na string do XML — se o remapeamento de rIds for uma permutação (ex. rId2↔rId3), um replace sequencial de string corrompe os dois (bug real que já apareceu aqui).
Reempacotar os executáveis
- Linux (
dist-linux/PlanoDeAtaque):pip install openpyxl python-pptx lxml pyinstallerepyinstaller --onefile --name PlanoDeAtaque gerar_plano.py. - Windows (
dist-windows/): este ambiente não tem Wine nem acesso a uma máquina Windows, então NÃO dá para compilar um.exede verdade aqui. A solução usada foi montar um Python "portátil": baixar opython-3.11.9-embed-amd64.zipoficial do python.org, habilitar site-packages editandopython311._pth(adicionarLib\site-packageseimport site), e usarpip download --platform win_amd64 --python-version 311 --implementation cp --abi cp311 --only-binary=:all: <pacotes>para baixar as wheels certas (mesmo rodando de um Linux —pip downloadsó baixa o arquivo, não executa nada) e extrair (unzip) cada wheel dentro depython/Lib/site-packages. Lançador é um.batsimples chamandopython\python.exe gerar_plano.py. Depois de montar, gerar de novo oPlanoDeAtaque_windows.zip(cuidado: gerar o zip DENTRO da própria pasta que está sendo zipada faz o zip se autoincluir — gerar em/tmpe mover para dentro depois).
Como validar antes de entregar
Como não há LibreOffice/PowerPoint disponível neste ambiente para renderizar
o .pptx, a validação usada foi:
- Integridade:
zipfile.testzip()+lxml.etree.fromstringem cada.xml/.relsdo pacote. - Reconstrução visual manual: ler
off/ext(EMU) de cada<p:pic>e forma colorida do slide viapython-pptx, converter para pixels e compor com Pillow (Image.alpha_composite) para gerar um PNG de conferência — é assim que os desalinhamentos e cores erradas foram detectados nesta conversa.
Git neste repositório
Não existe identidade de git configurada globalmente por padrão aqui. Em vez
de rodar git config --global (proibido pelas regras de segurança do
ambiente), usar override só para o comando do commit:
git -c user.name="..." -c user.email="..." commit -m "...".
.uploads/ está no .gitignore (são anexos de conversa, não arquivos do
projeto) — os artefatos finais que importam ficam em dist-linux/ e
dist-windows/.