# TypeScript SDK

## Instalacao

Requer Node.js >= 18.

```bash
npm install prismadata
```

O pacote e publicado em CJS + ESM com tipos TypeScript embutidos — funciona em
projetos JavaScript ou TypeScript sem dependencias extras.

## Quick Start

```typescript
import { PrismaDataClient } from "prismadata";

const client = new PrismaDataClient({ apiKey: "ak_xxxxxxxxxxxxxxxx" });

// Consultar favela mais proxima
const result = await client.slum(-23.55, -46.63);
console.log(result);

// Geocodificar um endereco
const geo = await client.geocode({ fullAddress: "Av Paulista 1000, Sao Paulo" });
console.log(geo);
```

Todos os metodos sao assincronos (`Promise`). O SDK nao expoe variante sincrona —
use `await` ou `.then()`.

## Autenticacao

O SDK aceita 3 formas de credencial (em ordem de prioridade):

**1. API Key direta:**

```typescript
const client = new PrismaDataClient({ apiKey: "ak_xxxxxxxxxxxxxxxx" });
```

**2. Usuario e senha:**

```typescript
const client = new PrismaDataClient({
  username: "user@email.com",
  password: "senha",
});
```

**3. Variaveis de ambiente:**

```bash
export PRISMADATA_APIKEY=ak_xxxxxxxxxxxxxxxx
# ou
export PRISMADATA_USERNAME=user@email.com
export PRISMADATA_PASSWORD=senha
```

```typescript
const client = new PrismaDataClient(); // detecta automaticamente
```

A renovacao do token JWT e automatica — o SDK cuida internamente. A autenticacao
e iniciada de forma "eager" no construtor (concluida antes do primeiro request).

## Configuracao do Cliente

```typescript
const client = new PrismaDataClient({
  apiKey: "ak_xxxxxxxxxxxxxxxx",
  timeout: 30,            // Timeout por request em segundos (default: 30)
  cache: true,            // Cache em memoria (default: false)
  cacheTtl: 86400,        // TTL do cache em segundos (default: 86400)
  cleanColumns: false,    // Remove prefixo prismadata__ das respostas (default: false)
  appName: "my-app",      // Enviado como header X-App em cada request
});
```

O helper `cleanColumns` tambem e exposto como funcao standalone:

```typescript
import { cleanColumns } from "prismadata";

const cleaned = cleanColumns(result);
// "prismadata__favela__distancia_m" -> "favela_distancia_m"
```

## Operacoes em Lote

Para consultas em volume, use os metodos `*Batch`:

```typescript
const points = {
  p1: [-23.55, -46.63] as [number, number],
  p2: [-22.90, -43.17] as [number, number],
  p3: [-19.92, -43.94] as [number, number],
};

const slums = await client.slumBatch(points);
const prisons = await client.prisonBatch(points);
const borders = await client.borderBatch(points);
const sectors = await client.infoscBatch(points);

// Renda estatica em lote (percentil e faixa salarial)
const incomeStatic = await client.incomeStaticBatch(points);

// Renda PDF em lote (distribuicao de probabilidade)
const incomePdf = await client.incomePdfBatch(points);

// Renda PDF com parametros demograficos por ponto
const incomePdfDetailed = await client.incomePdfBatchDetailed({
  p1: { lat: -23.55, lng: -46.63, gender: "M", age: 50 },
  p2: { lat: -22.90, lng: -43.17, gender: "F", age: 30, is_responsible: true },
});

// Multiplos servicos numa unica chamada (agregador)
const aggregated = await client.aggregateBatch(points, {
  services: ["slum", "prison", "infosc"],
});
```

O SDK divide automaticamente lotes grandes em chunks (1024 por chunk em endpoints
de localizacao, 16 em endpoints de roteamento) e prossegue com os chunks
restantes em caso de falha parcial.

### Geocodificacao em Lote

```typescript
// Geocodificar enderecos em lote
const addresses = {
  p1: { endereco_completo: "Av Paulista 1000, Sao Paulo, SP" },
  p2: { cep: "30130-165", numero: "100", logradouro: "Rua da Bahia" },
};
const geocoded = await client.geocodeBatch(addresses);

// Geocodificacao reversa em lote
const points = {
  p1: [-23.55, -46.63] as [number, number],
  p2: [-22.90, -43.17] as [number, number],
};
const reversed = await client.reverseGeocodeBatch(points);

// Geocodificacao + agregador em lote
const enriched = await client.geocodeAggregateBatch(addresses, {
  services: ["slum", "infosc"],
});
```

### Progresso em Lote

Todos os metodos `*Batch` aceitam um callback opcional `onProgress`:

```typescript
await client.slumBatch(points, (completed, total) => {
  console.log(`${completed}/${total}`);
});
```

## Aglomeracoes Comerciais

Aglomeracoes comerciais sao poligonais continuas de tecido comercial denso
(~11k regioes no Brasil), com atributos como qtd de PDVs, score, porte, vocacao,
faixa de renda e trechos de rua ranqueados.

```typescript
// Aglomeracao + trecho de rua mais proximo de um ponto
const result = await client.commercialCluster(-23.5613, -46.6558);

// Top 3 aglomeracoes proximas (resposta indexada por 1_, 2_, 3_)
const top3 = await client.commercialCluster(-23.5613, -46.6558, {
  topN: 3,
  radiusKm: 10.0,
});

// Filtrar por vocacao (aceita string ou array) e porte minimo
const filtered = await client.commercialCluster(-23.5613, -46.6558, {
  vocation: ["Moda", "Alimentacao"],
  minSize: "Grande",
});

// Enriquecimento de carteira de PDVs (ate 1024 pontos por request)
const carteira = {
  pdv_1: [-23.5613, -46.6558] as [number, number],
  pdv_2: [-22.9068, -43.1729] as [number, number],
};
const batchResult = await client.commercialClusterBatch(carteira);
// batchResult = { pdv_1: { ...campos flat... }, pdv_2: { ... } }

// Drill-down: detalhes de uma aglomeracao + todos os trechos
const detail = await client.commercialClusterDetail("5ebd0eb4f6b4");

// Geometria (opcional, premium): GeoJSON object ou WKT string.
// Requer role `admin` ou `viewer:commercial_cluster` no JWT — fale com vendas.
const geojson = await client.commercialCluster(-23.5613, -46.6558, {
  includeGeometry: true,
});
const geom = geojson.prismadata__commercial_cluster__geometria;
// geom e objeto: { type: "Polygon", coordinates: [[[...]]] }

const wkt = await client.commercialCluster(-23.5613, -46.6558, {
  includeGeometry: true,
  wkt: true,
});
// wkt.prismadata__commercial_cluster__geometria e string: "POLYGON((...))"

// Sem a role: lanca GeometriaNaoAutorizadaError (HTTP 403).
// Batch nao suporta geometria — use single point ou detail.
// Forcar incluir_geometria em batch lanca GeometriaBatchNaoSuportadoError.

// Granularidade fina: trecho de rua mais proximo (raio em metros)
const street = await client.commercialClusterStreet(-23.5613, -46.6558, {
  radiusM: 200,
});

// Busca por filtros (sem ponto geografico) — lista paginada
const search = await client.commercialClusterSearch(
  {
    state: ["SP", "RJ"],
    vocation: ["Moda"],
    size: ["Grande"],
    income_range: ["Premium", "Luxo"],
    min_pdv_count: 500,
    bbox: { min_lat: -24, min_lng: -47, max_lat: -23, max_lng: -46 },
  },
  { limit: 50 },
);

// Ranking top-N por criterio e escopo
const topSp = await client.commercialClusterRanking({
  criterio: "score",
  escopo: "uf",
  uf: "SP",
  topN: 20,
});
```

Para combinar `commercial_cluster` com outros produtos numa unica chamada,
use o aggregator (caminho preferido para enriquecimento de carteira):

```typescript
// Por ponto
const byPoint = await client.aggregate(-23.5613, -46.6558, {
  services: ["commercial_cluster", "slum", "prison"],
  commercial_cluster_top_n: 3,
  commercial_cluster_raio_km: 10.0,
});

// Por endereco textual (geocoda + enriquece)
const byAddress = await client.geocodeAggregate({
  fullAddress: "Av Paulista 1000, Sao Paulo, SP",
  services: ["commercial_cluster", "slum"],
});

// Batch
const batchAgg = await client.aggregateBatch(carteira, {
  services: ["commercial_cluster", "border"],
  commercial_cluster_top_n: 1,
});
```

## Modo Sandbox

O modo sandbox e detectado automaticamente a partir do claim `sandbox` no JWT —
nao ha flag explicita no cliente nem header `X-Sandbox` a passar. Tokens
emitidos como sandbox retornam dados sinteticos em todos os endpoints, sem
consumir quota real. Quando o token e sandbox, o SDK emite um `console.warn`
no startup avisando o usuario.

## Tratamento de Erros

Todos os erros estendem `PrismaDataError`, que carrega `statusCode`, `body` e
`headers` (quando disponiveis).

```typescript
import {
  PrismaDataClient,
  AuthenticationError,
  BatchError,
  GeometriaBatchNaoSuportadoError,
  GeometriaNaoAutorizadaError,
  PrismaDataError,
  QuotaExhaustedError,
  RateLimitError,
} from "prismadata";

try {
  const client = new PrismaDataClient({ apiKey: "ak_xxxxxxxxxxxxxxxx" });
  const result = await client.slum(-23.55, -46.63);
} catch (error) {
  if (error instanceof QuotaExhaustedError) {
    console.log("Quota mensal esgotada — nao sera feito retry");
  } else if (error instanceof RateLimitError) {
    console.log("Rate limit excedido apos retries — aguarde e tente novamente");
  } else if (error instanceof GeometriaNaoAutorizadaError) {
    console.log("Sem a role viewer:commercial_cluster — fale com vendas");
  } else if (error instanceof AuthenticationError) {
    console.log("Credenciais invalidas");
  } else if (error instanceof PrismaDataError) {
    console.log(`Erro da API (${error.statusCode}): ${error.message}`);
  }
}
```

### Falhas Parciais em Batch

Quando um lote grande falha parcialmente, o SDK coleta os resultados dos chunks
que deram certo e lanca `BatchError` com os dados parciais:

```typescript
try {
  const result = await client.slumBatch(largePointDict);
} catch (error) {
  if (error instanceof BatchError) {
    // Resultados dos chunks que deram certo
    for (const [key, value] of Object.entries(error.partialResults)) {
      process(key, value);
    }
    // Chaves dos chunks que falharam (para retry manual)
    console.log(`Falhou: ${error.failedKeys.join(", ")}`);
  }
}
```

O SDK implementa retry automatico com backoff exponencial para erros transientes
(429, 500, 502, 503, 504). Alem disso, respeita os headers `Retry-After` e
`X-RateLimit-Reset` do servidor. Quota esgotada (`X-Quota-Remaining: 0`) **nao**
e re-tentada — o erro `QuotaExhaustedError` e lancado imediatamente.

### Quota

Apos qualquer request bem-sucedido, `client.quotaInfo` expoe o estado mais
recente da quota (a partir dos headers `X-Quota-*`) ou `null` quando o tenant
nao possui politica de quota:

```typescript
await client.slum(-23.55, -46.63);
console.log(client.quotaInfo);
// { limit: 100000, used: 1234, remaining: 98766, period: "month", reset: 1717200000 }
```

---

- Índice geral: https://prismadata.io/llms.txt
- Documentação Redoc interativa: https://prismadata.io/redoc.html
