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 * MARGEMArmadilhas
⚠️ 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écnicoAdicionar campo depende_de e verificar antes de cortar.
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.