mozak.tech Engenharia de IA Corporativa 87%

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

4.10 — Portfólio e Objetivos Estratégicos com Simulação de Impacto (OKRs + IA)

Objetivo da Aula

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

Implementar simularImpactoKR(iniciativa, okrs, client) para avaliar contribuição de uma iniciativa nos KRs

Implementar rebalancearPortfolio(projetos, restricoes, client) com restrições de orçamento e capacidade

Criar matriz de contribuição iniciativa × KR com pesos numéricos

Completar os 2 TODOs do starter

Aplicar análise de portfólio para decisões de corte e priorização

Por que isso importa

OKRs são definidos no início do trimestre e esquecidos até o final. Portfolio sem análise de contribuição tem projetos que não movem nenhum Key Result — eles parecem úteis mas são desperdício de capacidade. Simulação de impacto responde: “Se cortarmos projeto X, quais OKRs são afetados e quanto?”

Conceitos Fundamentais

TODO 1: simularImpactoKR(iniciativa, okrs, client)

async function simularImpactoKR(iniciativa, okrs, client) {

  const okrs_str = okrs.map((o, i) =>

    `O${i+1}: ${o.objetivo}\n  KRs: ${o.key_results.map((kr, j) => `KR${i+1}.${j+1}: ${kr.descricao} (atual: ${kr.atual}/${kr.meta})`).join(', ')}`

  ).join('\n\n')

  

  const response = await client.messages.create({

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

    max_tokens: 600,

    system: `Você é um estrategista de produto. Analise como a iniciativa contribui para os OKRs.

Retorne JSON:

{

  "contribuicoes": [

    {"kr_id": "KR1.1", "impacto": 0-10, "justificativa": "..."}

  ],

  "okrs_nao_impactados": ["O2", "O3"],

  "recomendacao": "Alta/Média/Baixa prioridade",

  "justificativa_geral": "..."

}`,

    messages: [{

      role: 'user',

      content: `Iniciativa: ${iniciativa.nome}

Descrição: ${iniciativa.descricao}

Orçamento: R$ ${iniciativa.orcamento?.toLocaleString('pt-BR') || 'N/A'}

Time: ${iniciativa.time_size || 'N/A'} pessoas



OKRs do trimestre:

${okrs_str}`,

    }],

  })

  

  const texto = response.content[0].text

  const i = texto.indexOf('{'), f = texto.lastIndexOf('}') + 1

  const analise = JSON.parse(texto.slice(i, f))

  

  // Score: média ponderada das contribuições

  const score_total = analise.contribuicoes.reduce((acc, c) => acc + c.impacto, 0) / Math.max(analise.contribuicoes.length, 1)

  

  return {

    iniciativa: iniciativa.nome,

    ...analise,

    score_portfolio: Math.round(score_total * 10) / 10,

  }

}

TODO 2: rebalancearPortfolio(projetos, restricoes, client)

async function rebalancearPortfolio(projetos, restricoes, client) {

  const { orcamento_total, capacidade_max_pessoas } = restricoes

  

  // Ordenar por score se disponível

  const por_prioridade = [...projetos].sort((a, b) => (b.score_portfolio || 0) - (a.score_portfolio || 0))

  

  // Selecionar projetos dentro dos limites

  let orcamento_usado = 0

  let capacidade_usada = 0

  const portfolio_otimizado = []

  const cortados = []

  

  for (const projeto of por_prioridade) {

    const orca = projeto.orcamento || 0

    const cap = projeto.time_size || 0

    

    if (orcamento_usado + orca <= orcamento_total && capacidade_usada + cap <= capacidade_max_pessoas) {

      portfolio_otimizado.push(projeto)

      orcamento_usado += orca

      capacidade_usada += cap

    } else {

      cortados.push({

        projeto: projeto.nome,

        motivo: orcamento_usado + orca > orcamento_total ? 'orçamento' : 'capacidade',

        score: projeto.score_portfolio,

      })

    }

  }

  

  // Gerar recomendação narrativa

  const response = await client.messages.create({

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

    max_tokens: 300,

    messages: [{

      role: 'user',

      content: `Portfolio selecionado: ${portfolio_otimizado.map(p => p.nome).join(', ')}

Projetos cortados: ${cortados.map(c => `${c.projeto} (${c.motivo})`).join(', ')}

Orçamento usado: R$ ${orcamento_usado.toLocaleString('pt-BR')} / R$ ${orcamento_total.toLocaleString('pt-BR')}

Capacidade: ${capacidade_usada}/${capacidade_max_pessoas} pessoas



Explique a lógica do rebalanceamento em 2-3 frases.`,

    }],

  })

  

  return {

    portfolio_selecionado: portfolio_otimizado,

    projetos_cortados: cortados,

    utilizacao: {

      orcamento: `R$ ${orcamento_usado.toLocaleString('pt-BR')} / R$ ${orcamento_total.toLocaleString('pt-BR')}`,

      capacidade: `${capacidade_usada}/${capacidade_max_pessoas} pessoas`,

    },

    recomendacao_narrativa: response.content[0].text,

  }

}

Aprofundamento Técnico

Matriz de Contribuição Portfolio × OKR

function gerarMatrizContribuicao(analises_por_iniciativa) {

  const krs_unicos = [...new Set(

    analises_por_iniciativa.flatMap(a => a.contribuicoes.map(c => c.kr_id))

  )]

  

  const matriz = analises_por_iniciativa.map(analise => {

    const linha = { iniciativa: analise.iniciativa }

    for (const kr of krs_unicos) {

      const contrib = analise.contribuicoes.find(c => c.kr_id === kr)

      linha[kr] = contrib?.impacto || 0

    }

    return linha

  })

  

  // Adicionar linha de totais por KR

  const totais = { iniciativa: 'TOTAL' }

  for (const kr of krs_unicos) {

    totais[kr] = matriz.reduce((acc, row) => acc + (row[kr] || 0), 0)

  }

  

  return [...matriz, totais]

}

Exemplos Anotados

Exemplo 1: 3 Iniciativas × 2 OKRs

const okrs = [{

  objetivo: 'Aumentar receita 20%',

  key_results: [

    { descricao: 'GMV mensal de R$ 1M', atual: 750000, meta: 1000000 },

    { descricao: 'Taxa de conversão 5%', atual: 3.2, meta: 5 },

  ],

}]



const iniciativas = [

  { nome: 'Checkout otimizado', orcamento: 50000, time_size: 3 },

  { nome: 'Marketing de afiliados', orcamento: 100000, time_size: 2 },

  { nome: 'Personalização de preços', orcamento: 80000, time_size: 4 },

]



const restricoes = { orcamento_total: 180000, capacidade_max_pessoas: 7 }



const analises = await Promise.all(iniciativas.map(i => simularImpactoKR(i, okrs, client)))

// → Checkout otimizado: score 8.5 (alto impacto em conversão)

// → Afiliados: score 6.0 (impacto em GMV)

// → Personalização: score 7.0 (impacto em ambos)



const portfolio = await rebalancearPortfolio(analises.map((a, i) => ({

  ...iniciativas[i],

  score_portfolio: a.score_portfolio,

})), restricoes, client)

// → Seleciona: Checkout (R$50k, 3p) + Personalização (R$80k, 4p) = R$130k, 7 pessoas ✓

// → Corta: Afiliados (ultrapassa capacidade: 3+2+4=9 > 7)

Padrões e Armadilhas

Padrões

Padrão 1: Iniciativas sem contribuição em nenhum KR = candidatos a corte

const sem_impacto = analises.filter(a => a.score_portfolio < 2)

Padrão 2: Rebalancear com margem de segurança

const MARGEM = 0.9  // usar no máximo 90% do orçamento disponível

const orcamento_efetivo = orcamento_total * MARGEM

Armadilhas

⚠️ Armadilha 1: LLM superestima impacto de iniciativas favoritas LLMs tendem a pontuar alto “IA” e “dados” mesmo quando a conexão com o KR é tênue. Peça justificativa obrigatória e revise pontuações acima de 8.

⚠️ Armadilha 2: Score de portfólio sem normalização Iniciativa com 10 KRs relevantes parece ter score maior que com 2 KRs. Normalize dividindo pela quantidade de KRs.

⚠️ Armadilha 3: Cortar projeto com dependências

Iniciativa A corta → Iniciativa B perde pré-requisito técnico

Adicionar campo depende_de e verificar antes de cortar.

⚗ 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 — simularImpactoKR(iniciativa, okrs, client): passe descrição da iniciativa e lista de KRs para o LLM, peça JSON com contribuicoes[] e score_portfólio.

TODO 2 — rebalancearPortfolio(projetos, restricoes, client): ordene por score, selecione greedy dentro do orçamento e capacidade, gere narrativa de justificativa.

Agora você está pronto para o lab.