<!doctype html>
<html lang="pt-BR">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Abitai CRM Extractor</title>
    <link rel="stylesheet" href="/styles.css" />
  </head>
  <body>
    <main class="app-shell">
      <aside class="workspace-sidebar" aria-label="Controles do CRM Extractor">
        <header class="product-header">
          <p class="eyebrow">Abitai</p>
          <h1>CRM Extractor</h1>
          <p class="product-subtitle">Conversas em perfis estruturados para o CRM.</p>
          <button id="auth-button" class="auth-button" type="button" hidden>Entrar</button>
        </header>

        <nav class="mode-switch" aria-label="Área de trabalho">
          <button id="mode-catalog-button" class="mode-button is-active" type="button">
            Base data-shared
          </button>
          <button id="mode-upload-button" class="mode-button" type="button">
            Upload
          </button>
          <button id="mode-methodology-button" class="mode-button" type="button">
            Metodologia
          </button>
        </nav>

        <section id="catalog-controls" class="sidebar-section" aria-label="Catálogo">
          <div class="field search-field">
            <label for="conversation-search">Conversa da base</label>
            <input
              id="conversation-search"
              class="text-input"
              autocomplete="off"
              placeholder="Buscar por ID, telefone ou nome"
            />
            <div id="conversation-results" class="result-list" role="listbox"></div>
          </div>

          <p id="catalog-summary" class="catalog-summary">Carregando conversas...</p>
          <div id="selected-summary" class="selected-summary muted">
            Nenhuma conversa selecionada.
          </div>
        </section>

        <section id="upload-controls" class="sidebar-section" aria-label="Upload CSV" hidden>
          <div class="field">
            <label for="csv-upload-input">Arquivos CSV</label>
            <input
              id="csv-upload-input"
              class="file-input"
              type="file"
              accept=".csv,text/csv"
              multiple
            />
          </div>
          <div id="upload-summary" class="selected-summary muted">
            Nenhum arquivo selecionado.
          </div>
          <div id="upload-list" class="upload-list empty-state">
            Os arquivos selecionados aparecem aqui.
          </div>
        </section>

        <section id="methodology-controls" class="sidebar-section methodology-nav" hidden>
          <p class="section-kicker">Guia da ferramenta</p>
          <a href="#methodology-service">O serviço</a>
          <a href="#methodology-platform">Como operar</a>
          <a href="#methodology-flow">Fluxo input → output</a>
          <a href="#methodology-api">Usando a API</a>
          <a href="#methodology-parameters">Parâmetros dos modelos</a>
          <a href="#methodology-measures">Custo e latência</a>
          <a href="#methodology-glossary">Glossário</a>
          <a href="#methodology-dictionary">Formulário ativo</a>
        </section>

        <section id="shared-run-controls" class="run-controls" aria-label="Configuração da extração">
          <div id="selection-lock" class="selection-lock" hidden></div>

          <div class="control-grid">
            <div class="field">
              <label for="framework-select">Framework</label>
              <select id="framework-select" class="select-input"></select>
            </div>
            <div class="field">
              <label for="provider-select">Provider</label>
              <select id="provider-select" class="select-input"></select>
            </div>
            <div class="field full-width">
              <label for="model-select">Modelo</label>
              <select id="model-select" class="select-input"></select>
            </div>
            <div class="field">
              <label for="temperature-input">Temperatura</label>
              <input
                id="temperature-input"
                class="text-input parameter-input"
                type="number"
                inputmode="decimal"
                step="0.1"
              />
            </div>
            <div class="field">
              <label for="reasoning-select">Reasoning</label>
              <select id="reasoning-select" class="select-input"></select>
            </div>
          </div>

          <div class="button-row">
            <button id="estimate-button" class="btn secondary" type="button">Estimar</button>
            <button id="run-button" class="btn primary" type="button">
              <span class="button-label">Executar</span>
              <span class="button-spinner" aria-hidden="true"></span>
            </button>
          </div>

          <div id="selection-pricing" class="selection-pricing" aria-live="polite">
            Selecione um modelo para consultar o custo por token.
          </div>

          <dl id="run-status" class="status-grid">
            <div>
              <dt>
                Status
                <span class="metric-help" tabindex="0" aria-label="Estado atual da operação no navegador.">
                  <span class="info-icon" aria-hidden="true">i</span>
                  <span class="field-tooltip" role="tooltip">Indica se a interface está pronta, processando ou se encontrou um erro.</span>
                </span>
              </dt>
              <dd id="status-text">Pronto</dd>
            </div>
            <div>
              <dt>
                Estimate
                <span class="metric-help" tabindex="0" aria-label="Custo previsto ou observado da extração.">
                  <span class="info-icon" aria-hidden="true">i</span>
                  <span class="field-tooltip" role="tooltip">Mostra o custo previsto antes da chamada ou o custo calculado após a execução.</span>
                </span>
              </dt>
              <dd id="estimate-text">-</dd>
            </div>
            <div>
              <dt>
                Último run
                <span class="metric-help" tabindex="0" aria-label="Latência da execução mais recente.">
                  <span class="info-icon" aria-hidden="true">i</span>
                  <span class="field-tooltip" role="tooltip">Exibe a latência medida pelo serviço e, quando disponível, o tempo total observado no navegador.</span>
                </span>
              </dt>
              <dd id="latency-text">-</dd>
            </div>
            <div>
              <dt>
                Store
                <span class="metric-help" tabindex="0" aria-label="Situação da persistência do CRM.">
                  <span class="info-icon" aria-hidden="true">i</span>
                  <span class="field-tooltip" role="tooltip">Informa se o resultado foi somente estimado ou se o CRM foi persistido no store configurado.</span>
                </span>
              </dt>
              <dd id="store-text">-</dd>
            </div>
          </dl>
        </section>
      </aside>

      <section id="workspace-content" class="workspace-content" aria-label="Resultado da extração">
        <section class="workspace-panel transcript-panel" aria-label="Conversa limpa">
          <header class="panel-header">
            <div>
              <p id="transcript-eyebrow" class="eyebrow">Conversa limpa</p>
              <h2 id="conversation-title">Selecione uma conversa</h2>
            </div>
            <span id="message-count-badge" class="metric-badge">0 mensagens</span>
          </header>
          <div id="transcript" class="scroll-surface empty-state">
            As mensagens aparecem aqui.
          </div>
        </section>

        <section class="workspace-panel comparison-panel" aria-label="Comparação entre CRM extraído e Golden Set">
          <header class="panel-header">
            <div>
              <p class="eyebrow">Comparação por campo</p>
              <h2>CRM extraído e Golden Set</h2>
            </div>
            <div class="comparison-header-actions">
              <span id="comparison-badge" class="metric-badge">Sem comparação</span>
              <label class="toggle">
                <input id="json-toggle" type="checkbox" />
                <span>JSON</span>
              </label>
            </div>
          </header>
          <div class="comparison-column-headings" aria-hidden="true">
            <div>
              <span>CRM extraído</span>
              <strong id="crm-title">Framework(versão) / Provider / Modelo</strong>
            </div>
            <div>
              <span>Golden Set</span>
              <strong id="gold-title">Referência</strong>
            </div>
          </div>
          <div id="comparison-output" class="scroll-surface empty-state">
            Selecione uma conversa para comparar os campos.
          </div>
        </section>
      </section>

      <section id="methodology-panel" class="methodology-panel" aria-label="Metodologia" hidden>
        <header class="methodology-hero">
          <p class="eyebrow">Metodologia</p>
          <h2>Entenda antes de executar.</h2>
          <p>
            Este guia explica o que o CRM Extractor faz, como operar cada fluxo
            e como interpretar parâmetros, custo e latência sem misturar estimativas
            com medidas observadas.
          </p>
          <dl class="version-strip">
            <div>
              <dt>Metodologia</dt>
              <dd id="methodology-version">-</dd>
            </div>
            <div>
              <dt>Schema CRM</dt>
              <dd id="dictionary-version">-</dd>
            </div>
            <div>
              <dt>Contrato</dt>
              <dd id="methodology-contract">-</dd>
            </div>
          </dl>
        </header>

        <article id="methodology-service" class="guide-section">
          <p class="section-index">01</p>
          <div class="guide-copy">
            <h3>O serviço</h3>
            <p>
              O CRM Extractor recebe uma conversa imobiliária e transforma somente
              informações sustentadas pelo texto em campos estruturados do perfil
              do comprador.
            </p>
            <div class="boundary-grid">
              <div>
                <h4>O que ele faz</h4>
                <ul>
                  <li>Limpa e organiza as mensagens recebidas.</li>
                  <li>Extrai preferências, contexto financeiro e próximos passos.</li>
                  <li>Valida o JSON no contrato e salva o CRM quando há store.</li>
                  <li>Entrega custo, tokens, latência e identidade do setup.</li>
                </ul>
              </div>
              <div>
                <h4>O que ele não faz</h4>
                <ul>
                  <li>Não é o runtime do chat, WhatsApp ou voz.</li>
                  <li>Não cria gabaritos nem substitui revisão humana.</li>
                  <li>Não executa chamadas pagas sem habilitação do operador.</li>
                  <li>Não trata estimativas como cobrança final do provider.</li>
                </ul>
              </div>
            </div>
          </div>
        </article>

        <article id="methodology-platform" class="guide-section">
          <p class="section-index">02</p>
          <div class="guide-copy">
            <h3>Como operar</h3>
            <ol class="workflow-list">
              <li><strong>Escolha a entrada.</strong> Busque uma conversa da base ou envie um ou mais CSVs.</li>
              <li><strong>Confira o setup.</strong> Framework, provider e modelo determinam a execução.</li>
              <li><strong>Estime primeiro.</strong> A estimativa calcula tokens e custo sem chamar o provider.</li>
              <li><strong>Execute.</strong> Providers pagos dependem de <code>CRM_EXTRACTOR_LIVE=1</code>.</li>
              <li><strong>Leia o CRM.</strong> Cada campo informa valor, status, certeza e evidência.</li>
              <li><strong>Compare.</strong> Casos revisados mostram o Golden Set ao lado do resultado.</li>
            </ol>
            <div class="flow-line" aria-label="Fluxo do serviço">
              <span>Conversa</span>
              <span>Limpeza</span>
              <span>Extração</span>
              <span>Validação</span>
              <span>CRM</span>
              <span>Benchmark</span>
            </div>
          </div>
        </article>

        <article id="methodology-flow" class="guide-section io-flow-section">
          <p class="section-index">03</p>
          <div class="guide-copy">
            <h3>Do input ao CRM</h3>
            <p>
              A LLM fica atrás do adapter do provider, no backend do CRM Extractor.
              O agente consumidor chama nossa API; somente o serviço conhece as chaves
              da OpenAI, Gemini ou OpenRouter e transforma a conversa em um prompt de
              extração estruturada.
            </p>

            <div class="io-stage-flow" aria-label="Fluxo interno da extração">
              <section>
                <span>01</span>
                <strong>API</strong>
                <p>Recebe <code>conversation_id</code> ou <code>messages</code> e a seleção do setup.</p>
              </section>
              <section>
                <span>02</span>
                <strong>Entrada</strong>
                <p>Valida mensagens, resolve o formulário e organiza <code>chunk</code> ou <code>turn</code>.</p>
              </section>
              <section>
                <span>03</span>
                <strong>Framework</strong>
                <p>Monta instruções, campos permitidos, formato JSON e trecho da conversa.</p>
              </section>
              <section class="llm-stage">
                <span>04</span>
                <strong>Provider / LLM</strong>
                <p>O adapter envia o prompt e recebe somente as anotações estruturadas.</p>
              </section>
              <section>
                <span>05</span>
                <strong>Contrato</strong>
                <p>Faz parse, rejeita dados inválidos, completa ausentes, consolida e valida.</p>
              </section>
              <section>
                <span>06</span>
                <strong>CRM</strong>
                <p>Retorna o contrato versionado, metadados operacionais e persiste quando configurado.</p>
              </section>
            </div>

            <div class="io-contract-grid">
              <section>
                <header>
                  <p class="measure-label">Input da nossa API</p>
                  <h4>O consumidor envia</h4>
                </header>
                <pre><code>{
  "conversation_id": "synthetic_001",
  "provider": "gemini",
  "model_id": "gemini-2.5-flash",
  "framework": "current_house",
  "mode": "chunk",
  "form_ref": "real_estate_buyer_profile@4",
  "temperature": 0,
  "reasoning_effort": "thinking_budget_0",
  "messages": [
    {
      "message_ordinal": 1,
      "speaker": "customer",
      "text": "Quero um apartamento em Campinas."
    }
  ]
}</code></pre>
                <p>
                  <code>messages</code> pode ser omitido quando
                  <code>conversation_id</code> existe no catálogo configurado. As chaves
                  dos providers nunca fazem parte desse payload.
                </p>
              </section>

              <section>
                <header>
                  <p class="measure-label">Input da LLM</p>
                  <h4>O framework compila</h4>
                </header>
                <pre><code>Instruções de extração
+ referência do formulário e campos permitidos
+ formato JSON obrigatório
+ regras de evidência
+ chunk da conversa com ordinal, speaker e text
+ temperatura, reasoning e limite de saída</code></pre>
                <p>
                  Esse conteúdo é enviado pelo adapter em
                  <code>src/crm_extractor/providers/</code>. O frontend e o agente não
                  chamam a LLM diretamente.
                </p>
              </section>

              <section>
                <header>
                  <p class="measure-label">Output da LLM</p>
                  <h4>Resposta intermediária</h4>
                </header>
                <pre><code>{
  "field_annotations": [
    {
      "field_id": "preferred_city",
      "status": "captured",
      "value": ["Campinas"],
      "evidence": [
        {"message_ordinal": 1, "speaker": "customer", "text": "..."}
      ],
      "reviewer_note": null
    }
  ]
}</code></pre>
                <p>
                  A resposta bruta não é o contrato público nem é persistida. O
                  serviço faz parse, valida os campos permitidos e preenche os campos
                  <code>missing</code> de forma determinística.
                </p>
              </section>

              <section>
                <header>
                  <p class="measure-label">Output da nossa API</p>
                  <h4>Resposta final validada</h4>
                </header>
                <pre><code>{
  "conversation_id": "synthetic_001",
  "stored": true,
  "metadata": {
    "setup_id": "...",
    "provider": "gemini",
    "model_id": "gemini-2.5-flash",
    "cost_usd": 0.000000,
    "latency_ms": 0
  },
  "crm": {
    "contract_id": "buyer_profile_runtime_result_v1",
    "contract_version": "1",
    "form_ref": {
      "form_id": "real_estate_buyer_profile",
      "form_version": "4"
    },
    "field_annotations": [
      {
        "field_id": "preferred_city",
        "status": "captured",
        "value": ["Campinas"],
        "evidence": [
          {"message_ordinal": 1, "speaker": "customer", "text": "..."}
        ],
        "reviewer_note": null,
        "certainty_score": 1.0
      }
    ]
  }
}</code></pre>
                <p>
                  O exemplo está abreviado; a resposta real contém os 39 campos na
                  ordem do formulário. Cada campo final possui <code>field_id</code>,
                  <code>status</code>, <code>value</code>, <code>evidence</code>, nota de
                  revisão e certeza.
                </p>
              </section>
            </div>

            <div class="provider-boundary">
              <header>
                <p class="measure-label">Fronteira das LLMs</p>
                <h4>Providers e modelos publicados pelo ambiente</h4>
              </header>
              <div id="methodology-provider-boundary">
                <p class="empty-state">Carregando providers...</p>
              </div>
              <p class="method-note">
                O adapter encapsula endpoint, autenticação, retries, timeout e leitura
                de tokens. Depois da resposta, o fluxo volta ao código local para parse,
                merge, validação e persistência.
              </p>
            </div>
          </div>
        </article>

        <article id="methodology-api" class="guide-section">
          <p class="section-index">04</p>
          <div class="guide-copy">
            <h3>Usando a API</h3>
            <p>
              Integre pelo contrato publicado pelo próprio serviço. Consulte as
              opções em cada inicialização, estime antes de executar e mantenha
              tokens de acesso somente no backend da aplicação consumidora.
            </p>

            <div class="api-origin-line">
              <span>Base URL pública</span>
              <code>https://crm-extractor-demo.abitai.com.br</code>
            </div>

            <ol class="api-sequence">
              <li>
                <strong>Descubra o ambiente</strong>
                <span>Valide o health e leia modelos, parâmetros, autenticação e política em <code>/api/v1/options</code>.</span>
              </li>
              <li>
                <strong>Prepare a autenticação</strong>
                <span>Se <code>auth.enabled=true</code>, envie um access token Cognito no header <code>Authorization</code>.</span>
              </li>
              <li>
                <strong>Estime sem chamar o modelo</strong>
                <span>Use <code>/api/v1/estimate</code> ou <code>/api/v1/estimate-csv-batch</code> e confirme custo e orçamento.</span>
              </li>
              <li>
                <strong>Execute e persista a resposta</strong>
                <span>Use <code>/api/v1/extract</code> somente após a estimativa e guarde <code>setup_id</code>, custo, tokens e latência.</span>
              </li>
            </ol>

            <div class="api-reference-links" aria-label="Contratos da API">
              <a href="/api/v1/openapi.json" target="_blank" rel="noreferrer">Abrir OpenAPI</a>
              <a href="/api/v1/options" target="_blank" rel="noreferrer">Ver opções atuais</a>
              <a href="/api/v1/contract" target="_blank" rel="noreferrer">Ver contrato completo</a>
            </div>

            <div class="api-example">
              <header>
                <p class="measure-label">1 · Descoberta</p>
                <h4>Confirme que o serviço está pronto</h4>
              </header>
              <pre><code>export CRM_EXTRACTOR_BASE_URL="https://crm-extractor-demo.abitai.com.br"

curl --fail-with-body "$CRM_EXTRACTOR_BASE_URL/api/v1/health"
curl --fail-with-body "$CRM_EXTRACTOR_BASE_URL/api/v1/options"</code></pre>
            </div>

            <div class="api-example">
              <header>
                <p class="measure-label">2 · Payload JSON</p>
                <h4>Crie <code>request.json</code> com mensagens autorizadas</h4>
              </header>
              <pre><code>{
  "conversation_id": "synthetic_api_example",
  "provider": "openai",
  "model_id": "gpt-5-mini",
  "framework": "current_house",
  "mode": "chunk",
  "reasoning_effort": "low",
  "max_output_tokens": 4096,
  "messages": [
    {
      "message_ordinal": 1,
      "speaker": "customer",
      "text": "Quero um apartamento de dois quartos em Campinas."
    }
  ]
}</code></pre>
            </div>

            <div class="api-example-grid">
              <section class="api-example">
                <header>
                  <p class="measure-label">3 · Estimativa</p>
                  <h4>Não chama o provider</h4>
                </header>
                <pre><code>curl --fail-with-body \
  -X POST "$CRM_EXTRACTOR_BASE_URL/api/v1/estimate" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $CRM_EXTRACTOR_ACCESS_TOKEN" \
  --data @request.json</code></pre>
              </section>

              <section class="api-example">
                <header>
                  <p class="measure-label">4 · Extração</p>
                  <h4>Executa o modelo habilitado</h4>
                </header>
                <pre><code>curl --fail-with-body \
  -X POST "$CRM_EXTRACTOR_BASE_URL/api/v1/extract" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $CRM_EXTRACTOR_ACCESS_TOKEN" \
  --data @request.json</code></pre>
              </section>
            </div>

            <p class="method-note">
              Omita o header <code>Authorization</code> quando
              <code>auth.enabled=false</code>. Nunca envie chaves da OpenAI, Gemini
              ou OpenRouter: os segredos dos providers pertencem ao serviço.
            </p>

            <div class="api-example">
              <header>
                <p class="measure-label">CSV · Lote</p>
                <h4>Estime um arquivo antes da extração</h4>
              </header>
              <pre><code>curl --fail-with-body \
  -X POST "$CRM_EXTRACTOR_BASE_URL/api/v1/estimate-csv-batch" \
  -H "Authorization: Bearer $CRM_EXTRACTOR_ACCESS_TOKEN" \
  -F "provider=openai" \
  -F "model_id=gpt-5-mini" \
  -F "framework=current_house" \
  -F "mode=chunk" \
  -F "budget_usd_ceiling=5" \
  -F "file=@conversas.csv"</code></pre>
              <p>
                Para executar o mesmo lote, troque a rota por
                <code>/api/v1/extract-csv-batch</code> e envie
                <code>response_format=inline_json</code> ou <code>zip</code>.
              </p>
            </div>

            <div class="api-endpoint-wrap">
              <table class="api-endpoint-table">
                <caption>Rotas essenciais</caption>
                <thead>
                  <tr><th>Rota</th><th>Uso</th><th>Scope quando autenticado</th></tr>
                </thead>
                <tbody>
                  <tr><td><code>GET /api/v1/options</code></td><td>Descobrir configuração executável</td><td>Público</td></tr>
                  <tr><td><code>GET /api/v1/conversations</code></td><td>Pesquisar conversas da base</td><td><code>crm-extractor/read</code></td></tr>
                  <tr><td><code>GET /api/v1/crms/{conversation_id}</code></td><td>Ler o último CRM salvo</td><td><code>crm-extractor/read</code></td></tr>
                  <tr><td><code>POST /api/v1/estimate</code></td><td>Estimar uma conversa sem provider</td><td><code>crm-extractor/estimate</code></td></tr>
                  <tr><td><code>POST /api/v1/extract</code></td><td>Executar uma extração</td><td><code>crm-extractor/execute</code></td></tr>
                  <tr><td><code>POST /api/v1/memory/update</code></td><td>Atualizar memória no modo turn</td><td><code>crm-extractor/execute</code></td></tr>
                </tbody>
              </table>
            </div>

            <div class="api-error-guide">
              <h4>Como tratar respostas</h4>
              <dl>
                <div><dt><code>401 missing_access_token</code></dt><dd>Obtenha ou renove o token Cognito e repita a chamada.</dd></div>
                <div><dt><code>400 selection_not_enabled</code></dt><dd>Escolha somente provider e modelo publicados em <code>execution_policy.enabled_provider_model_map</code>.</dd></div>
                <div><dt><code>404 not_found</code></dt><dd>O recurso ou a conversa não existe na fonte disponível.</dd></div>
                <div><dt><code>live_guardrail</code></dt><dd>O backend não está habilitado para chamadas pagas.</dd></div>
                <div><dt><code>provider_unavailable</code></dt><dd>O provider recusou, limitou ou não respondeu à chamada.</dd></div>
                <div><dt><code>storage_unavailable</code></dt><dd>O CRM store está temporariamente indisponível; não exponha detalhes de conexão.</dd></div>
              </dl>
              <p>
                Em respostas concluídas, persista <code>setup_id</code>,
                <code>crm</code>, <code>cost_calculation</code>,
                <code>provider_usage</code> e <code>latency_breakdown_ms</code>.
                Não grave conversas nem respostas brutas em logs.
              </p>
            </div>
          </div>
        </article>

        <article id="methodology-parameters" class="guide-section">
          <p class="section-index">05</p>
          <div class="guide-copy">
            <h3>Parâmetros dos modelos</h3>
            <p id="methodology-parameter-summary">
              Carregando o suporte publicado por <code>/api/v1/options</code>.
            </p>
            <div class="parameter-explainer">
              <section>
                <p class="measure-label">Temperatura</p>
                <h4>Controla a variação da resposta</h4>
                <p>
                  Valores próximos de <code>0</code> favorecem respostas mais estáveis e
                  repetíveis. Valores maiores aumentam a variação das escolhas do modelo e
                  podem reduzir a consistência do JSON. Para extração de CRM usamos
                  <code>0</code> como ponto inicial e comparamos outras temperaturas somente
                  em experimentos controlados.
                </p>
              </section>
              <section>
                <p class="measure-label">Thinking / reasoning</p>
                <h4>Define o esforço de raciocínio</h4>
                <p>
                  Níveis maiores permitem mais processamento interno antes da resposta e
                  podem ajudar em conversas ambíguas, mas tendem a aumentar tokens, custo e
                  latência. Cada modelo publica suas opções válidas; modelos sem suporte
                  recebem a chamada sem esse parâmetro.
                </p>
              </section>
            </div>
            <div id="methodology-parameter-table" class="parameter-support-table"></div>
            <p class="method-note">
              O catálogo mostra capacidade técnica. O ambiente pode fixar um valor de
              thinking ou bloquear alterações por política de execução.
            </p>
          </div>
        </article>

        <article id="methodology-measures" class="guide-section">
          <p class="section-index">06</p>
          <div class="guide-copy">
            <h3>Como ler custo e latência</h3>
            <div class="measure-grid">
              <section>
                <p class="measure-label">Custo</p>
                <h4>Fonte antes do número</h4>
                <p>
                  <code>estimated</code> usa tokens previstos e preços versionados.
                  <code>provider_usage</code> usa tokens devolvidos pela chamada.
                  <code>billing_actual</code> é uma reconciliação por geração, não a
                  fatura consolidada da conta.
                </p>
              </section>
              <section>
                <p class="measure-label">Latência</p>
                <h4>Ponta a ponta e por etapa</h4>
                <p>
                  O backend mede carregamento, chunking, prompt, provider, parsing,
                  merge, validação e persistência. <code>service_total_ms</code> é o
                  tempo completo observado pelo serviço.
                </p>
              </section>
            </div>
            <p class="method-note">
              Compare somente setups com a mesma cobertura de casos, schema, prompt,
              modo e parâmetros. Falhas e conversas concluídas devem ficar visíveis.
            </p>
          </div>
        </article>

        <article id="methodology-glossary" class="guide-section">
          <p class="section-index">07</p>
          <div class="guide-copy">
            <h3>Glossário essencial</h3>
            <dl class="glossary-grid">
              <div><dt>setup_id</dt><dd>Identidade do framework, provider, modelo, modo, formulário e parâmetros.</dd></div>
              <div><dt>chunk</dt><dd>Modo primário que processa a conversa e consolida um CRM.</dd></div>
              <div><dt>message_by_message</dt><dd>Experimento que processa uma mensagem por chamada e consolida o resultado.</dd></div>
              <div><dt>turn</dt><dd>Atualização incremental do CRM usando uma janela recente.</dd></div>
              <div><dt>captured</dt><dd>Há valor explícito e evidência suficiente na conversa.</dd></div>
              <div><dt>ambiguous</dt><dd>Há sinal relevante, mas o valor ainda não é seguro.</dd></div>
              <div><dt>missing</dt><dd>A conversa não sustenta um valor para o campo.</dd></div>
              <div><dt>Golden Set</dt><dd>CRM revisado por humanos usado como gabarito de qualidade.</dd></div>
              <div><dt>provider</dt><dd>Empresa ou gateway que executa a chamada do modelo.</dd></div>
              <div><dt>framework</dt><dd>Implementação que monta prompts e produz o contrato.</dd></div>
            </dl>
          </div>
        </article>

        <article id="methodology-dictionary" class="guide-section dictionary-section">
          <p class="section-index">08</p>
          <div class="guide-copy">
            <h3>Formulário ativo</h3>
            <p>
              Este é o modelo que orienta a extração. A visualização vem diretamente
              de <code>/api/v1/methodology</code> e mostra tipos, regras de captura,
              aliases, normalização e valores canônicos da versão ativa.
            </p>

            <dl class="form-summary">
              <div><dt>Referência</dt><dd id="methodology-form-ref">-</dd></div>
              <div><dt>Campos</dt><dd id="methodology-form-field-count">-</dd></div>
              <div><dt>Locale</dt><dd id="methodology-form-locale">-</dd></div>
              <div><dt>Contrato de saída</dt><dd id="methodology-form-contract">-</dd></div>
            </dl>

            <div class="form-toolbar">
              <label class="form-search" for="methodology-form-search">
                <span class="sr-only">Buscar campo do formulário</span>
                <input
                  id="methodology-form-search"
                  class="text-input"
                  type="search"
                  autocomplete="off"
                  placeholder="Buscar por campo, descrição ou alias"
                />
              </label>
              <div class="form-view-switch" role="tablist" aria-label="Visualização do formulário">
                <button id="methodology-form-fields-button" class="is-active" type="button" role="tab" aria-controls="methodology-form-fields-view" aria-selected="true">Campos</button>
                <button id="methodology-form-json-button" type="button" role="tab" aria-controls="methodology-form-json-view" aria-selected="false">JSON</button>
              </div>
            </div>

            <div id="methodology-form-fields-view" role="tabpanel">
              <p id="methodology-form-result-count" class="form-result-count">Carregando campos...</p>
              <div id="methodology-dictionary-list" class="form-field-list"></div>
            </div>
            <pre id="methodology-form-json-view" class="form-json-view" role="tabpanel" hidden><code>{}</code></pre>
          </div>
        </article>

        <footer class="methodology-footer">
          A API continua expondo relatórios sanitizados em
          <code>/api/v1/analysis/*</code>, mas esta interface possui somente as páginas
          Base data-shared, Upload e Metodologia.
        </footer>
      </section>

    </main>
    <div id="toast" class="toast" role="status" aria-live="polite" hidden></div>
    <script src="/app.js"></script>
  </body>
</html>
