mozak.tech Engenharia de IA Corporativa 76%

Parte IV — Gestão de Projetos e Produto com Suporte de IA

4.6 — Transcrição, Resumo e Ações de Reunião com Assistência de IA (Meeting AI)

Objetivo da Aula

Ao concluir esta aula, você será capaz de:

Implementar resumo automático de transcrições de reunião via LLM

Extrair action items estruturados (responsável, prazo, prioridade) de texto livre

Criar páginas no Notion via API com action items extraídos

Completar os 2 TODOs: resumirReuniao e criarPaginaNotion

Integrar transcrição → resumo → Notion em pipeline automático

Por que isso importa

A maioria das reuniões gera uma lista de “próximos passos” que ninguém persiste. Action items discutidos verbalmente têm taxa de conclusão de ~30%. Quando criados automaticamente no Notion com responsável e prazo, sobem para ~70% (dado de pesquisa de productividade de times).

LLM extrai action items melhor do que humanos porque não filtra subjetivamente: anota tudo que tem estrutura “quem faz o quê até quando”.

Conceitos Fundamentais

TODO 1: resumirReuniao(transcricao, client)

async function resumirReuniao(transcricao, client) {

  const response = await client.messages.create({

    model: 'claude-haiku-4-5-20251001',

    max_tokens: 800,

    system: `Você é um assistente especializado em gestão de projetos.

Analise a transcrição e retorne JSON com esta estrutura exata:

{

  "resumo": "Resumo executivo em 2-3 frases",

  "decisoes": ["Decisão 1", "Decisão 2"],

  "action_items": [

    {

      "descricao": "O que deve ser feito",

      "responsavel": "Nome ou 'A definir'",

      "prazo": "Data ou 'A definir'",

      "prioridade": "alta|media|baixa"

    }

  ],

  "proxima_reuniao": "Data/hora ou 'Não definida'"

}`,

    messages: [{

      role: 'user',

      content: `Transcrição da reunião:\n${transcricao}`,

    }],

  })

  

  const texto = response.content[0].text

  const inicio = texto.indexOf('{')

  const fim = texto.lastIndexOf('}') + 1

  return JSON.parse(texto.slice(inicio, fim))

}

TODO 2: criarPaginaNotion(dados, notionToken)

async function criarPaginaNotion(dados, notionToken, databaseId) {

  // Notion API - criar página num database

  const response = await fetch('https://api.notion.com/v1/pages', {

    method: 'POST',

    headers: {

      'Authorization': `Bearer ${notionToken}`,

      'Notion-Version': '2022-06-28',

      'Content-Type': 'application/json',

    },

    body: JSON.stringify({

      parent: { database_id: databaseId },

      properties: {

        Name: { title: [{ text: { content: `Reunião ${new Date().toLocaleDateString('pt-BR')}` } }] },

        Status: { select: { name: 'Pendente' } },

      },

      children: [

        {

          object: 'block',

          type: 'heading_2',

          heading_2: { rich_text: [{ text: { content: 'Resumo' } }] },

        },

        {

          object: 'block',

          type: 'paragraph',

          paragraph: { rich_text: [{ text: { content: dados.resumo } }] },

        },

        {

          object: 'block',

          type: 'heading_2',

          heading_2: { rich_text: [{ text: { content: 'Action Items' } }] },

        },

        ...dados.action_items.map(item => ({

          object: 'block',

          type: 'to_do',

          to_do: {

            rich_text: [{

              text: {

                content: `[${item.prioridade.toUpperCase()}] ${item.descricao} — ${item.responsavel} (${item.prazo})`,

              },

            }],

            checked: false,

          },

        })),

      ],

    }),

  })

  

  const resultado = await response.json()

  return { sucesso: response.ok, url: resultado.url, id: resultado.id }

}

Aprofundamento Técnico

Notion API: Estrutura de Database vs Page

Notion workspace

├── Database (tabela de reuniões)

│   ├── Page (reunião 1) → tem properties + children (blocks)

│   ├── Page (reunião 2)

│   └── ...

└── Page standalone (sem database)

Para criar páginas num database: parent: { database_id: "..." }. Para criar página avulsa: parent: { page_id: "..." }.

O databaseId vem da URL do Notion: notion.so/workspace/<database_id>?v=...

Mock para Desenvolvimento

async function criarPaginaNotionMock(dados, notionToken, databaseId) {

  console.log('NOTION MOCK — Página que seria criada:')

  console.log(JSON.stringify({

    database: databaseId,

    resumo: dados.resumo,

    total_action_items: dados.action_items.length,

    itens_alta_prioridade: dados.action_items.filter(i => i.prioridade === 'alta').length,

  }, null, 2))

  return { sucesso: true, url: 'https://notion.so/mock', id: 'mock-id' }

}

Exemplos Anotados

Exemplo 1: Pipeline Completo

const transcricao = `

Maria: Ok, precisamos lançar o módulo de pagamentos antes do Q2.

João: Eu fico responsável pela integração com Stripe.

Maria: João, você consegue ter isso pronto até sexta-feira?

João: Sim, até sexta. Pedro, você pode fazer a revisão de segurança na semana que vem?

Pedro: Pode ser, terça ou quarta.

Maria: Decidido então: lançamento no dia 30.

`



// Passo 1: resumir

const resumo = await resumirReuniao(transcricao, client)

// → {

//   resumo: "Decisão de lançar módulo de pagamentos no Q2...",

//   action_items: [

//     { descricao: "Integrar Stripe", responsavel: "João", prazo: "sexta-feira", prioridade: "alta" },

//     { descricao: "Revisão de segurança", responsavel: "Pedro", prazo: "terça/quarta", prioridade: "media" },

//   ]

// }



// Passo 2: criar no Notion

const pagina = await criarPaginaNotion(resumo, process.env.NOTION_TOKEN, process.env.NOTION_DB_ID)

console.log(`Reunião salva: ${pagina.url}`)

Padrões e Armadilhas

Padrões

Padrão 1: Sempre Mock primeiro

const USE_NOTION = process.env.NOTION_TOKEN && process.env.NOTION_DB_ID

const criarPagina = USE_NOTION ? criarPaginaNotion : criarPaginaNotionMock

Padrão 2: Action items sem responsável definido

const indefinidos = resumo.action_items.filter(i => i.responsavel === 'A definir')

if (indefinidos.length > 0) {

  console.warn(`⚠️ ${indefinidos.length} action items sem responsável`)

}

Armadilhas

⚠️ Armadilha 1: Notion rate limit A API aceita ~3 requests/segundo. Para muitos action items, adicione delay entre chamadas.

⚠️ Armadilha 2: LLM inventa prazos Se a transcrição não menciona prazo, use "A definir" — nunca invente uma data. Adicione no system prompt: "Se não mencionar prazo explicitamente, use 'A definir'.".

⚠️ Armadilha 3: JSON com vírgulas em ações

"descricao": "Integrar Stripe, Braintree, e PayPal"

// vírgulas dentro de string são OK — o problema é quando LLM gera JSON malformado

// sempre use try/catch no JSON.parse
⚗ Laboratório prático — mozak.tech
Se não for realizar o laboratório, pule para o próximo capítulo.

Ponte para o Lab

TODO 1 — resumirReuniao(transcricao, client): system prompt instruindo JSON com resumo, decisoes, action_items[] (com responsavel, prazo, prioridade), proxima_reuniao.

TODO 2 — criarPaginaNotion(dados, notionToken, databaseId): POST para api.notion.com/v1/pages, com parent.database_id, properties e children blocks com to_do items.

Agora você está pronto para o lab.