Ir para o conteúdo

RFC Técnico: Briefings Personalizados

Status: Proposta aprovada para implementação Data: 2026-06-21 Arquivos afetados: briefing.py, engine.py, server.py, app.js, index.html


1. Estado atual do código

briefing_routine() — o que já existe

  • Busca os últimos 10 eventos via store.recent_events(limit=10)
  • Filtra apenas eventos com payload.text
  • Monta ctx_block com as 5 primeiras notas
  • Usa prompt fixo: "Gere um briefing das notas recentes. Seja direto e pessoal."
  • Chama DeepSeek com tool_use forçado (return_briefing)
  • Salva o resultado como evento kind="briefing" no ContextStore
  • Já aceita action_id e context como parâmetros — mas context nunca é usado no prompt

/api/routine/{routine_id} — o que já existe

  • action_id e context do body JSON
  • Passa os dois para run_routine()briefing_routine()
  • Lacuna: não lê template do body — nunca passou esse campo

runRoutine() no frontend

  • Faz POST para /api/routine/briefing com body {}
  • Lacuna: não passa nenhum template — body sempre vazio

2. Mudanças mínimas no briefing.py

2a. Dict de templates (substitui o prompt fixo)

BRIEFING_TEMPLATES: dict[str, dict] = {
    "geral": {
        "label": "Geral",
        "prompt": (
            "Gere um briefing das notas recentes. "
            "Seja direto e pessoal. Destaque padrões, pendências e o que mais chamou atenção."
        ),
    },
    "saude": {
        "label": "Saúde",
        "prompt": (
            "Foque em saúde, bem-estar, energia e hábitos. "
            "Identifique padrões, alertas e sugira um próximo passo concreto."
        ),
    },
    "trabalho": {
        "label": "Trabalho",
        "prompt": (
            "Foque em projetos, tarefas e entregas. "
            "Liste o que está em aberto e o que está avançando."
        ),
    },
    "amanha": {
        "label": "Amanhã",
        "prompt": (
            "Com base nas notas, identifique pendências e itens inacabados. "
            "Monte uma lista do que vale atacar amanhã, em ordem de impacto."
        ),
    },
}

_DEFAULT_TEMPLATE = "geral"

2b. Assinatura atualizada da função

def briefing_routine(
    store,
    llm_fn=None,
    action_id: str | None = None,
    context: str = "",
    template: str = "geral",          # <-- único parâmetro novo
) -> RoutineResult:

2c. Lógica de seleção de prompt (substitui 1 linha)

# ANTES:
prompt = "Gere um briefing das notas recentes. Seja direto e pessoal."

# DEPOIS:
tpl = BRIEFING_TEMPLATES.get(template, BRIEFING_TEMPLATES[_DEFAULT_TEMPLATE])
prompt = tpl["prompt"]

2d. Injeção de perfil (retrocompatível, zero mudança de schema)

Inserir após montar ctx_block, antes de chamar o LLM:

# Buscar perfil se existir (evento kind="setup" com payload.profile)
events_all = store.recent_events(limit=200)
profile = next(
    (e["payload"].get("profile") for e in events_all if e.get("kind") == "setup"),
    None,
)

system = f"Você é a Brisa, segunda mente pessoal. {ctx_block}"
if profile:
    name = profile.get("name", "")
    areas = ", ".join(profile.get("areas", []))
    tone = profile.get("tone", "direto")
    if name:
        system += f"\nO usuário se chama {name}."
    if areas:
        system += f" Áreas de foco: {areas}."
    if tone:
        system += f" Tom desejado: {tone}."

Quando profile é None (caso atual de 100% dos usuários): zero impacto, sem branch extra, sem erro.


3. Mudança no engine.py

Uma linha: repassar template do run_routine() para a rotina.

# engine.py — run_routine()
def run_routine(
    routine_id: str,
    store,
    llm_fn=None,
    action_id=None,
    context: str = "",
    template: str = "geral",    # <-- novo
) -> RoutineResult:
    ...
    return routine_fn(store, llm_fn=llm_fn, action_id=action_id, context=context, template=template)

4. Mudança no server.py

Uma linha: ler template do body e passar para run_routine.

# server.py — api_routine()
action_id = body.get("action_id")
context   = body.get("context", "")
template  = body.get("template", "geral")    # <-- nova linha

result = run_routine(routine_id, store, action_id=action_id, context=context, template=template)

5. UI — seleção de template (máximo 10 linhas de HTML)

HTML (inserir dentro do .card-header, antes do botão + Novo)

<div class="briefing-tabs" id="briefing-tabs">
  <button class="btab active" onclick="setBriefingTab('geral',this)">Geral</button>
  <button class="btab" onclick="setBriefingTab('saude',this)">Saúde</button>
  <button class="btab" onclick="setBriefingTab('trabalho',this)">Trabalho</button>
  <button class="btab" onclick="setBriefingTab('amanha',this)">Amanhã</button>
</div>

JS (adicionar 6 linhas no app.js)

var _briefingTemplate = 'geral';
function setBriefingTab(tpl, el) {
  _briefingTemplate = tpl;
  document.querySelectorAll('#briefing-tabs .btab').forEach(function(b){ b.classList.remove('active'); });
  el.classList.add('active');
}

Alterar runRoutine('briefing') para passar o template

// Trocar a linha do fetch body em runRoutine():
body: JSON.stringify(routineId === 'briefing' ? {template: _briefingTemplate} : {})

CSS (3 linhas, inline no <style> existente)

.briefing-tabs { display:flex; gap:6px; margin:6px 0 2px; }
.btab { background:none; border:none; color:var(--dim); font-size:13px; cursor:pointer; padding:2px 8px; border-radius:4px; }
.btab.active { color:var(--fg); background:var(--surface2); font-weight:600; }

6. Estimativa de esforço

Tarefa Arquivo Esforço
Dict BRIEFING_TEMPLATES + seleção de prompt briefing.py 20 min
Injeção de perfil (busca evento setup) briefing.py 20 min
Repassar template na cadeia engine.py + server.py 10 min
Tabs HTML + JS + CSS index.html + app.js 30 min
Testes manuais (4 templates, com/sem perfil) 20 min
Total ~1h40

Sem migrações. Sem novos endpoints. Sem novos modelos. Retrocompatível: qualquer cliente que não mande template recebe o comportamento atual ("geral").


7. O que NÃO fazer (scope cut)

  • Banco de templates customizáveis — dict estático é suficiente
  • Onboarding de perfil obrigatório — perfil é opt-in
  • Templates por vertente financeira — fora do escopo desta entrega
  • Briefings agendados automáticos — é rotina manual, não cron
  • Editor de templates no frontend — escopo PO avançado