Sistema de Orçamento para Projetos Completo (全案设计报价系统)
Esta skill renderiza dados de lista de quantidades em orçamentos PDF profissionais, com suporte a capa, tabela de preços total e páginas de detalhes de múltiplas páginas.
1. Quando usar esta Skill
Condições de disparo
As seguintes situações devem usar esta skill:
- Usuário digita
/quote— Gerar orçamento PDF - Usuário menciona que quer converter dados de orçamento de uma tabela online (Feishu) ou Excel para PDF
As seguintes situações não devem usar esta skill:
- Usuário está apenas editando dados na tabela online (deve usar a skill
lark-base) - Usuário está apenas visualizando histórico de orçamentos (não precisa regenerar)
Dependências prévias
- Skill
lark-base— Ler dados da tabela online do Feishu - Skill
lark-im— Enviar PDF para o chat do Feishu - Skill
lark-drive— Fazer upload do PDF para o drive do Feishu - Skill
xlsx— Analisar arquivos Excel (quando o usuário fornece Excel) - Ambiente Node.js + Playwright (já instalados no diretório do projeto)
Etapa 0: Verificação de primeiro uso
Quando o usuário usa a Skill pela primeira vez ou encontra erros relacionados a dependências ao executar comandos, o Agente deve executar o seguinte processo de verificação.
1. Verificar ambiente Node.js
Tente executar node --version, exigido >= 18. Se falhar ou a versão for muito baixa:
- Peça ao usuário para instalar Node.js (>= 18): https://nodejs.org
- Sugira usar nvm para gerenciar versões
2. Verificar navegador Playwright
Verifique se npx playwright install chromium foi concluído no diretório do projeto. Se o render.js reportar erro Executable doesn't exist:
- Execute no diretório do projeto:
npx playwright install chromium - Tente renderizar novamente
3. Verificar lark-cli
Tente executar lark-cli --version. Se falhar (command not found):
- Peça ao usuário para instalar o lark-cli (ferramenta de linha de comando do Feishu)
- Após instalar, execute:
lark-cli auth login
4. Verificar status de login do Feishu
Tente executar lark-cli base +table-list --base-token LfjJbLrTHacijesHmIjcDtSpnJf para testar o status de login. Se retornar erro de autenticação:
- Peça ao usuário para executar:
lark-cli auth login - Tente novamente
5. Fornecer modelo do Feishu
O usuário precisa copiar o modelo do projeto para seu próprio espaço do Feishu:
- Link do modelo: https://li1fn1sw90.feishu.cn/base/LfjJbLrTHacijesHmIjcDtSpnJf?from=from_copylink
- Guie o usuário para clicar no link → clicar em "Copiar esta tabela" → preencher dados do projeto e detalhes do orçamento em seu próprio espaço
- Após concluir, envie o link da nova tabela para o Agente
Processamento de dados de origem Excel
Quando o usuário fornece um arquivo Excel, não analise e renderize diretamente. Fluxo de orientação:
- Sugira: Recomenda-se primeiro importar para a tabela online do Feishu, os campos e a ordem ficam mais alinhados
- Guie o usuário para importar o Excel para o Feishu:
- Abra o Feishu → Nova tabela online → Importar → Selecionar arquivo Excel
- Ou copie diretamente a tabela modelo e cole os dados do Excel nela
- Após o usuário fornecer o link da tabela online, continue com o fluxo
/quote
2. Fluxo de trabalho
Usuário dispara → Verificação de ambiente → Confirmar fonte de dados → Ler dados → Validar dados → Confirmar Logo → Confirmar taxa → Confirmar template → Renderizar PDF → Enviar para Feishu
Etapa 1: Confirmar fonte de dados
O usuário deve fornecer um dos seguintes:
- Link da tabela online do Feishu — Ex:
https://xxx.feishu.cn/base/XXX - Arquivo Excel — Caminho de arquivo local ou enviado pelo chat
Se o usuário não fornecer, pergunte qual fonte de dados ele prefere.
Etapa 2: Ler dados
Fonte de dados: Tabela online do Feishu
- Extraia o
base-tokendo link fornecido pelo usuário (a parte após/base/na URL) - Liste as tabelas:
lark-cli base +table-list --base-token <token> - Encontre os
table_iddas duas tabelas: Informações do Projeto e Detalhes do Orçamento - Leia as informações do projeto:
lark-cli base +record-list --base-token <token> --table-id <table_id_Informações_do_Projeto> --format json - Primeiro, consulte as visualizações:
lark-cli base +view-list --base-token <token> --table-id <table_id_Detalhes_do_Orçamento>, pegue oview_idda visualização grid padrão (ex: "Todos os Itens") - Leia os detalhes do orçamento (deve usar
--view-id, caso contrário a ordem retornada pode não corresponder à ordem arrastada pelo usuário na interface do Feishu):lark-cli base +record-list --base-token <token> --table-id <table_id_Detalhes_do_Orçamento> --view-id <ID_da_visualização> --format json --limit 200 - Extraia das informações do projeto: Nome do Projeto, Número do Projeto, Data de Elaboração, Elaborador, Email de Contato, Logo da Empresa, Taxa de Imposto, Taxa de Administração
- Extraia dos detalhes do orçamento: Área, Categoria da Obra, Nome do Item, Características do Item, Unidade, Quantidade, Preço Unitário, Valor Total, Observações (Número de sequência não precisa ser lido, é gerado automaticamente na renderização)
Fonte de dados: Excel
Use a skill xlsx para analisar o Excel e extrair dados com a mesma estrutura. O Excel pode ter várias planilhas, é necessário encontrar a planilha que contém os detalhes do orçamento (geralmente com cabeçalhos como "Nº", "Categoria da Obra", "Preço Unitário", etc.).
Etapa 3: Validação de dados
Antes de renderizar o PDF, o Agente deve validar os dados lidos. Não pule esta etapa.
Lista de verificação (executar item por item):
3.1 Integridade das informações do projeto
Verifique os três campos: Nome do Projeto, Número do Projeto, Data de Elaboração:
- Qualquer um vazio → Informe ao usuário qual campo está faltando e peça para complementar na tabela "Informações do Projeto" do Feishu
- Todos preenchidos → Continue
3.2 Número de itens no orçamento
Verifique o array items:
- Se for 0 ou não existir → "Não há dados nos detalhes do orçamento. Adicione itens na tabela 'Detalhes do Orçamento' do Feishu e tente novamente."
- Se houver dados → Continue
3.3 Verificação campo a campo
Para cada registro, verifique os campos obrigatórios (Categoria da Obra, Nome do Item, Unidade, Quantidade, Preço Unitário) e gere um relatório resumido:
Resultado da validação:
- Total de itens: 104
- Área ausente: 3 (itens 6, 9, 20)
- Categoria da Obra ausente: 2 (itens 5, 18)
- Nome do Item ausente: 0
- Unidade ausente: 1 (item 12)
- Quantidade ausente: 0
- Preço Unitário ausente: 3 (itens 5, 7, 22)
- Categoria da Obra fora da lista padrão: 0
3.4 Tratamento de campos ausentes
- Itens com falha ≤ 20% do total: Informe ao usuário quais itens específicos estão com problema e pergunte "Deseja pular esses itens e continuar a geração?"
- Itens com falha > 20%: Peça ao usuário para corrigir os dados e tente novamente, não continue com a renderização
- Usuário confirma pular → Filtre os itens problemáticos e continue com os dados válidos
3.7 Verificação de área ausente (apenas ao classificar por área)
Quando o usuário escolhe classificar por área (Etapa 5.6, opção "Por Área"), verifique adicionalmente o campo Área de cada registro:
Área ausente: X (item X)
- Itens com falha ≤ 20% do total: Informe ao usuário quais itens estão sem área e pergunte "Deseja agrupar esses itens em 'Outros' e continuar?"
- Itens com falha > 20%: Peça ao usuário para complementar a área e tente novamente
- Usuário confirma → Itens sem área são agrupados em "Outros" (mutuamente excludente com a lógica de pular da Etapa 3.4: o usuário escolhe entre pular ou agrupar em Outros)
3.5 Correção automática do valor total
Para cada registro, verifique se o Valor Total é igual a Quantidade × Preço Unitário:
- Diferença < 1 yuan → Não faça nada
- Diferença ≥ 1 yuan ou vazio → Calcule automaticamente
Valor Total = Quantidade × Preço Unitárioe escreva o valor nos dados de renderização - Itens corrigidos > 10% → Aviso: "X itens com valor total corrigido automaticamente. Sugira verificar a fórmula na tabela online do Feishu."
3.6 Lista padrão de categorias de obra
Abaixo estão as 13 categorias padrão:
Medidas do Projeto, Demolição, Alvenaria, Concreto e Concreto Armado,
Estrutura Metálica, Impermeabilização, Isolamento Térmico, Revestimento de Pisos,
Revestimento de Paredes e Divisórias, Forro, Pintura e Revestimentos,
Outros Acabamentos, Instalações
Categorias que não estão na lista ainda podem ser renderizadas, mas informe ao usuário "A categoria 'XXX' não está na lista padrão."
Etapa 4: Processamento do Logo
Deve ser executado, não pode ser pulado. Independentemente da fonte de dados (tabela online ou Excel), o Logo deve ser verificado e processado.
- Ler da tabela online: Verifique o campo de anexo
Logo da Empresana tabela de Informações do Projeto- Se houver anexo, use a API media para baixar (
drive +downloadnão suporta anexos de tabela online):lark-cli api GET /open-apis/drive/v1/medias/{file_token}/download --output ./logo.png - Converta a imagem para data URI base64 e passe para o campo
logo_urlnos dados de renderização
- Se houver anexo, use a API media para baixar (
- Ler do Excel: Verifique se há imagem do logo no Excel (geralmente incorporada na planilha ou como anexo)
- Se houver, extraia e converta para data URI
- Logo ausente (deve perguntar ao usuário):
- Se não encontrar a imagem do logo na fonte de dados, deve perguntar ao usuário:
- "Não encontrei o Logo da empresa na fonte de dados. Como proceder?"
- Opção A: Enviar imagem do logo
- Opção B: Fornecer URL da imagem do logo
- Opção C: Não usar logo (o PDF exibirá a marca "R M" padrão)
- Não pule esta etapa por padrão, mesmo que o usuário diga "gerar direto", confirme como lidar com o logo
- Se não encontrar a imagem do logo na fonte de dados, deve perguntar ao usuário:
Etapa 5: Confirmar taxa de imposto e taxa de administração
Priorize o uso dos valores dos campos Taxa de Imposto e Taxa de Administração nas informações do projeto da tabela online do Feishu. Se a tabela online não tiver esses campos, pergunte ao usuário.
Regras de taxas (comum para taxa de imposto e taxa de administração):
- Valor > 1 → Tratar como porcentagem inteira, dividir automaticamente por 100 (ex:
3→ 3%,10→ 10%) - Valor ≤ 1 → Tratar como decimal diretamente (ex:
0.03→ 3%,0.1→ 10%) - Valor vazio: Taxa de imposto padrão 3%, Taxa de administração padrão 0 (não cobrada)
Ordem de cálculo:
Subtotal = Σ Valor Total de cada item
Taxa de Administração = Subtotal × Taxa de Administração
Imposto = (Subtotal + Taxa de Administração) × Taxa de Imposto
Total Geral = Subtotal + Taxa de Administração + Imposto
Quando a Taxa de Administração > 0, a tabela de preços exibe a linha "Taxa de Administração"; quando = 0, não exibe (compatível com dados antigos).
Etapa 5.5: Confirmar estilo do template
Deve perguntar, não pode pular. Mesmo que um template tenha sido usado anteriormente, confirme a cada vez.
Pergunte ao usuário para escolher o template:
Escolha o estilo do template para o orçamento:
1. Swiss IKB (padrão) — Capa azul com fundo cheio + títulos de categoria bilíngues
2. Swiss IKB Zebra — Como acima + linhas de detalhes com listras zebradas (branco/azul claro alternados)
3. B&W — Capa branca + ênfase cinza claro, ideal para impressão P&B
4. B&W Zebra — Como acima + linhas de detalhes com listras zebradas (branco/cinza claro alternados)
Se a resposta do usuário contiver um nome ou número de template claro, use o template correspondente:
swiss-ikb— Swiss IKB (padrão)swiss-ikb-zebra— Swiss IKB Zebrabw— B&W versão impressão P&Bbw-zebra— B&W Zebra versão impressão P&B com listras zebradas
Em desenvolvimento (não disponível externamente por enquanto): Os códigos dos quatro templates Editorial / Editorial B&W / Card / Card B&W já existem, aguardando modificações futuras do usuário para ativação. Se o usuário solicitar explicitamente o uso desses templates, pode-se executar comandos como
node scripts/render.js --template editorial, mas recomende prioritariamente os 4 templates acima já lançados.
Etapa 5.6: Confirmar método de classificação
Deve perguntar, não pode pular. Confirme antes de cada renderização, não use a opção anterior por padrão.
Pergunte ao usuário para escolher o método de classificação:
Escolha o método de classificação do orçamento:
1. Por Área (Entrada, Sala de Estar, Quarto Principal...) — Tabela de preços agrupa por área; na tabela de detalhes, adiciona coluna de Categoria da Obra após o número de sequência
2. Por Categoria de Obra (Medidas do Projeto, Demolição...) — Método tradicional; tabela de detalhes não inclui coluna de Categoria da Obra
Após o usuário responder com o número ou nome claro, use o modo correspondente:
area— Classificar por área, comando de renderização adiciona--group-by areacategory— Classificar por categoria de obra (padrão), não passar ou adicionar--group-by category
Nota: Ao escolher "Por Área", certifique-se de que o campo
Áreana tabela de detalhes do orçamento está preenchido (a Etapa 3.7 verificará). Se os dados ainda não tiverem área preenchida, recomende usar "Por Categoria de Obra" primeiro.
Etapa 5.7: Tradução de nomes de áreas para inglês (apenas ao classificar por área)
Os nomes de áreas variam de projeto para projeto. A tradução deve ser feita dinamicamente antes de cada renderização, proibido usar mapeamento fixo.
- Colete todos os nomes de áreas que aparecem nos detalhes do orçamento (sem duplicatas)
- O Agente traduz cada nome de área para o inglês (tradução usual na indústria de design, ex: 玄关→Foyer, 客厅→Living Room, 阳光房→Sunroom)
- Escreva o resultado da tradução como objeto
region_namesno JSON dos dados de renderização:
{
"region_names": {
"玄关": "Foyer",
"阳光房": "Sunroom"
}
}
- Durante a renderização, o
render.jsprioriza as traduções emregion_names(nomes de área não fornecidos retrocedem para o mapeamento interno de áreas comuns e, por último, exibem "AREA") - Após a renderização, liste a tabela de tradução de áreas usada. O usuário pode corrigir; na próxima cotação, a tradução será refeita.
Etapa 6: Renderizar PDF
Execute no diretório do projeto:
node scripts/render.js --input <data.json> --template <nome_do_template> --vat-rate <taxa_de_imposto> --group-by <area|category> --output ./output/<Nome_do_Projeto>_<Número_do_Projeto>.pdf
--group-by area— Classificar por área (grande classificação = área; detalhes incluem coluna de Categoria da Obra)--group-by categoryou não passar — Classificar por categoria de obra (comportamento original)
Formato JSON dos dados de renderização:
{
"项目名称": "xxx",
"工程编号": "xxx",
"编制日期": "xxx",
"编制人员": "xxx",
"联系邮箱": "xxx",
"logo_url": "data:image/png;base64,...",
"税率": 0.08,
"管理费": 0.1,
"items": [
{
"区域": "玄关",
"工程分类": "措施项目",
"项目名称": "脚手架",
"项目特征": "室内脚手架",
"单位": "项",
"数量": 1,
"综合单价": 3200,
"合价": 3200
}
]
}
Regra de geração automática de número de sequência: O campo
序号não precisa ser fornecido nos dados (pode não ter esta coluna na tabela online do Feishu). Na renderização, é gerado automaticamente no formatoNº do grupo.Nº dentro do grupo: Ao classificar por área, Área 01 →1.1, 1.2, …, Área 02 →2.1, 2.2, …; Ao classificar por categoria de obra, Medidas do Projeto →1.x, Demolição →2.x… Instalações →13.x. A ordem dentro do grupo = ordem dos registros no Feishu (pode ser arrastada na tabela online).
Taxa de imposto e taxa de administração (veja Etapa 5):
- Valor de
税率/管理费> 1 tratado como porcentagem inteira (3 → 3%), ≤ 1 tratado como decimal (0.08 → 8%) - Taxa de Administração = Subtotal × Taxa de Administração; Imposto = (Subtotal + Taxa de Administração) × Taxa de Imposto; Total Geral = Subtotal + Taxa de Administração + Imposto
- Se a Taxa de Administração não for fornecida ou for 0 → não cobrada, a tabela de preços não exibe a linha de Taxa de Administração
- Se o parâmetro
--vat-ratenão for fornecido, orender.jslê prioritariamente o campo税率dos dados
Etapa 7: Salvar e enviar
Após a renderização do PDF, deve perguntar ao usuário como deseja salvar:
PDF gerado: <Nome_do_Projeto>_<Número_do_Projeto>_<nome_do_template>.pdf (XX itens, ¥XXX com impostos)
Como deseja salvar?
1. Enviar para a janela de chat atual
2. Salvar no drive do Feishu e enviar o link
3. Ambos
Execute de acordo com a escolha do usuário:
Opção 1 — Enviar para a janela de chat
lark-cli im +messages-send --type media --file <caminho_do_pdf>
Opção 2 — Salvar no drive do Feishu
lark-cli drive +upload --file <caminho_do_pdf> --title "<Nome_do_Projeto>_<Número_do_Projeto>"
Obtenha o link do arquivo após o upload e responda:
Salvo no drive do Feishu: <Nome_do_Projeto>_<Número_do_Projeto>_<nome_do_template>.pdf
Link: https://xxx.feishu.cn/drive/xxx
Opção 3 — Ambos
Primeiro execute a Opção 2 (upload), depois a Opção 1 (enviar arquivo + link).
Limpeza de arquivos temporários
Após enviar/salvar, remova os arquivos JSON temporários e o arquivo de download do Logo (se houver) gerados durante a renderização.
3. Estrutura do projeto
quote-generator-skill/
├── package.json # Configuração do projeto
├── setup.sh # Script de instalação com um clique
├── SKILL.md # Definição da Skill
├── scripts/
│ ├── render.js # Motor de renderização HTML → PDF
│ ├── demo.js # Demonstração pronta para uso
│ └── generate-large-test.js # Gerador de dados de teste em grande escala
├── references/
│ ├── templates/
│ │ ├── content.html # Template Handlebars para PDF (página de conteúdo)
│ │ ├── cover-screen.html # Template de capa independente para Swiss IKB
│ │ ├── cover-bw.html # Template de capa independente para B&W
│ │ ├── swiss-ikb.json # Configuração Swiss IKB
│ │ ├── swiss-ikb-zebra.json # Configuração Swiss IKB Zebra
│ │ ├── bw.json # Configuração B&W
│ │ └── bw-zebra.json # Configuração B&W Zebra
│ ├── helpers.js # Helpers personalizados do Handlebars
│ └── bitable-config.json # Configuração de campos da tabela online
├── docs/plans/ # Documentos de design
├── output/ # Diretório de saída dos PDFs gerados
└── README.md
4. Descrição dos templates
O template PDF contém três tipos de página:
- Capa — Nome do projeto, número, data, elaborador, logo (canto superior direito)
- Tabela de preços — Resumo dos valores por categoria de obra + Subtotal/Imposto/Total Geral
- Páginas de detalhes — Cerca de 8 linhas de dados por página, com título da categoria, linhas de detalhes, linha de subtotal e número de página
5. Observações importantes
- O
Valor Totalna tabela online do Feishu é um campo de fórmula (=Quantidade×Preço Unitário), já calculado na leitura - O
Nº de sequêncianão precisa ser mantido no Feishu: é gerado automaticamente na renderização (Nº do grupo.Nº dentro do grupo, ordem dentro do grupo conforme a ordem dos registros). Para ajustar a ordem dos detalhes, arraste os registros na tabela online do Feishu Categoria da Obraé um campo de seleção única, as 13 opções correspondem às 13 categorias de obraÁreaé um campo de texto livre (sugere-se alterar para seleção única no Feishu: Entrada, Sala de Estar, Sala de Jantar, Cozinha, Quarto Principal, Quarto Secundário, Banheiro, Varanda, Escritório, Closet, Corredor, Casa Inteira, etc.), usado apenas ao classificar por área, pode ficar vazio- Os títulos dos grupos no modo área exibem o nome bilíngue (ex:
Living Room 客厅). Os nomes de área variam de projeto para projeto. O Agente traduz dinamicamente a cada renderização conforme Etapa 5.7 e escreve emregion_names; o mapeamento interno de áreas comuns serve apenas como fallback, exibindo "AREA" quando não houver correspondência Unidadeé um campo de seleção única, suporta extensão personalizada- O Logo suporta formato imagem (recomendado) e texto
- A taxa de imposto é especificada pelo usuário a cada vez, não é fixa
- O PDF de saída é tamanho A4, adequado para impressão
6. Tratamento de problemas comuns
| Erro | Causa | Ação do Agente |
|---|---|---|
command not found: lark-cli | lark-cli não instalado | Peça para instalar o lark-cli, consulte Etapa 0.3 |
lark-cli retorna erro de autenticação | Não logado ou token expirado | Peça para executar lark-cli auth login |
Executable doesn't exist | Playwright Chromium não instalado | Execute npx playwright install chromium |
| PDF renderizado vazio ou com formato confuso | Formato do JSON de dados incorreto | Verifique se Nome do Projeto e Número do Projeto não estão vazios e items tem pelo menos 1 item |
| Nº de sequência diferente do preenchido manualmente | Nº de sequência agora é gerado automaticamente | Fenômeno normal: O nº de sequência é recalculado automaticamente por grupo (ex: 01 Casa Inteira → 1.1, 1.2…). Para ajustar a ordem, arraste os registros no Feishu |
| Tamanho/fonte anormais | Muito raro (fontes incorporadas como WOFF2, não dependem de CDN externo) | Verifique se os arquivos de fonte no diretório references/fonts/ do projeto estão completos |
| Falha ao baixar Logo | Problema de permissão da API de anexos da tabela online | Confirme se o usuário autorizou, ou escolha "Não usar logo" |
base-token não pode ser extraído da URL | O que o usuário forneceu não é um link de tabela online | Peça ao usuário para fornecer um link de tabela online do Feishu (a URL deve conter /base/). |