# Extraction Strategy — Parser Expansion Roadmap

> **Date**: 2026-02-24
> **Context**: Dual-run validated, native parser matches ccusage on daily costs. Next: expand parser to extract rich metrics.

---

## Current State

O parser nativo (`_scan_jsonl_entries`) faz:
- Scan de `type: "assistant"` entries apenas
- Dedup por `message.id:requestId` (keep first)
- Extrai: timestamp, model, 4 token types, cost, project, session_id
- Popula: `live_entries`, `native_daily_usage`, `native_sessions`

Isto é suficiente para custos. Para métricas ricas, precisamos de expandir.

---

## Architecture: Two-Pass Strategy

### Pass 1: Cost Pass (actual — manter como está)
- Lê apenas `type: "assistant"`, dedup por message.id
- Extrai tokens e custo
- Rápido, ~2s para 383MB
- Alimenta tabelas de custo

### Pass 2: Metrics Pass (novo)
- Lê TODOS os tipos de entry
- Mantém estado por sessão (permission mode, model chain, turn sequence)
- Extrai metadata de content blocks (tool names, block types — NUNCA conteúdo)
- Alimenta tabelas de métricas novas
- Pode ser mais lento (5-10s) — é background, não importa

**Porquê dois passes?** O pass de custos precisa de ser rápido e bullet-proof. O pass de métricas pode falhar sem afectar nada. Separar garante que não partimos o que já funciona.

---

## Data Model: New Tables

### `native_turns`
Uma row por turn (user → assistant → tools → result cycle).

```sql
CREATE TABLE native_turns (
    session_file TEXT NOT NULL,     -- path relativo do JSONL
    turn_index INTEGER NOT NULL,   -- ordem no ficheiro
    timestamp TEXT NOT NULL,
    model TEXT,
    permission_mode TEXT,          -- default/acceptEdits/bypassPermissions
    stop_reason TEXT,              -- tool_use/stop_sequence
    duration_ms INTEGER,           -- de system:turn_duration
    input_tokens INTEGER DEFAULT 0,
    output_tokens INTEGER DEFAULT 0,
    cache_read_tokens INTEGER DEFAULT 0,
    cache_creation_tokens INTEGER DEFAULT 0,
    cost REAL DEFAULT 0,
    has_thinking BOOLEAN DEFAULT 0,
    tool_count INTEGER DEFAULT 0,  -- quantas tools chamou neste turn
    tools_used TEXT,               -- JSON array de tool names ["Bash","Read","Edit"]
    is_error BOOLEAN DEFAULT 0,    -- turn resultou em erro
    is_interrupted BOOLEAN DEFAULT 0,
    project TEXT,
    PRIMARY KEY (session_file, turn_index)
);
```

### `native_tool_usage`
Uma row por tool call individual.

```sql
CREATE TABLE native_tool_usage (
    session_file TEXT NOT NULL,
    turn_index INTEGER NOT NULL,
    tool_index INTEGER NOT NULL,   -- ordem dentro do turn
    timestamp TEXT NOT NULL,
    tool_name TEXT NOT NULL,        -- Bash, Read, Edit, etc.
    is_error BOOLEAN DEFAULT 0,    -- tool_result.is_error
    project TEXT,
    PRIMARY KEY (session_file, turn_index, tool_index)
);
```

### `native_session_meta`
Metadata agregada por sessão.

```sql
CREATE TABLE native_session_meta (
    session_file TEXT PRIMARY KEY,
    project TEXT,
    first_timestamp TEXT,
    last_timestamp TEXT,
    duration_seconds INTEGER,
    turn_count INTEGER,
    total_duration_ms INTEGER,     -- soma de turn_duration entries
    permission_modes TEXT,         -- JSON: {"default": 45, "bypassPermissions": 12}
    models_used TEXT,              -- JSON array
    tool_counts TEXT,              -- JSON: {"Bash": 23, "Read": 45, "Edit": 12}
    compaction_count INTEGER,
    error_count INTEGER,
    subagent_count INTEGER,
    is_subagent BOOLEAN DEFAULT 0,
    parent_session TEXT,
    git_branch TEXT,
    cli_version TEXT,
    total_cost REAL DEFAULT 0,
    total_tokens INTEGER DEFAULT 0
);
```

---

## Extraction Rules per Entry Type

### `type: "assistant"` — Pass 2 logic
1. Group streaming entries by `message.id`
2. Concatenate content blocks
3. Count: text blocks, thinking blocks, tool_use blocks
4. Extract tool names from tool_use blocks (`block.name`)
5. **NEVER** read `block.input`, `block.text`, `block.thinking`

### `type: "user"` — State tracking
1. Track `permissionMode` changes → store as session state
2. Count user messages (excluding tool_results) → turn boundaries
3. Detect interruptions: `content[0].text == "[Request interrupted by user]"`
4. From tool_result blocks: extract `is_error` flag only
5. **NEVER** read `content` text, `toolUseResult.stdout/content`

### `type: "system"` — Direct metadata
1. `subtype: "turn_duration"` → `durationMs` per turn
2. `subtype: "api_error"` → error count, retry patterns
3. `subtype: "compact_boundary"` → compaction events, `preTokens`
4. `subtype: "local_command"` → CLI command usage (e.g., /clear)

### `type: "progress"` — Skip entirely
Progress entries são updates de streaming de bash/agent execution. Não têm métricas úteis para o dashboard. Ignorar.

### `type: "file-history-snapshot"` — Skip entirely
Backups de ficheiros. Irrelevante para métricas.

### `type: "queue-operation"` — Skip entirely
Queue management interno. Irrelevante.

---

## Session Reconstruction Algorithm

Para extrair métricas por sessão, precisamos de reconstruir a sequência de turns:

```
1. Ler ficheiro JSONL completo
2. Separar entries por tipo
3. Para assistant entries: agrupar por message.id → mensagens completas
4. Construir timeline ordenada por timestamp:
   - USER entry (com permissionMode) → start of turn
   - ASSISTANT message (agrupada) → response
   - SYSTEM:turn_duration → turn metrics
   - SYSTEM:api_error → error events
   - SYSTEM:compact_boundary → compaction markers
5. Iterar timeline, manter estado:
   - current_permission_mode
   - turn_index (incrementa em cada USER entry)
   - model chain (sequência de modelos usados)
```

---

## YOLO Mode Detection — Specific Strategy

Temos o campo explícito `permissionMode: "bypassPermissions"`.

**Métricas YOLO:**
- % de turns em YOLO mode (por sessão, por projecto, all-time)
- Custo em YOLO mode vs custo em default mode
- Tools mais usadas em YOLO (hypothesis: mais Bash e Edit)
- Duração média de turn em YOLO vs default
- "YOLO streak" — turns consecutivos em YOLO mode

**Dashboard display:**
- Badge: "YOLO Coder 🔥" se >50% turns em YOLO
- Wrapped slide: "82% do teu código foi escrito em piloto automático"
- Comparação: "Em YOLO, gastas 3x mais por turn"

---

## Implementation Priority

### P0 — Ship First (direct fields, high viral value)
1. **YOLO mode %** — `permissionMode` directo, viral
2. **Tool breakdown** — tool names de content blocks, visual
3. **Turn duration** — `system:turn_duration`, directo
4. **Session duration** — first/last timestamp, trivial
5. **Turn count per session** — contar user entries

### P1 — Ship Second (moderate extraction)
6. **Cache hit rate** — `cache_read / (input + cache_read)`, por sessão
7. **Error rate** — contar `system:api_error`
8. **Compaction frequency** — contar `system:compact_boundary`
9. **Model switching** — distinct models por sessão + sequência
10. **Subagent depth** — ficheiros em subagents/ dirs

### P2 — Ship Later (complex, nice to have)
11. **Temporal patterns** — hora/dia da semana heatmap
12. **Git branch tracking** — field directo mas precisa de UI
13. **Web search frequency** — `server_tool_use` counts
14. **Retry patterns** — exponential backoff analysis
15. **Plan mode usage** — EnterPlanMode/ExitPlanMode tool calls
16. **CLI version tracking** — `version` field

---

## Risk Mitigation

### Performance
- Pass 2 corre em background thread, nunca bloqueia endpoints
- Se demorar >30s, implementar scan incremental (por mtime, como pass 1)
- Tables novas são write-only no background, read-only nos endpoints

### Data Safety
- Code review de cada field extraction contra a safety boundary
- Testes automáticos que verificam que nunca se extrai content/text
- Log de warnings se encontrar entries com formato inesperado

### Backward Compat
- Tabelas novas são independentes das existentes
- Se o pass 2 falhar, pass 1 continua — zero impacto em custos
- Endpoints novos (e.g., /api/tools, /api/yolo) são aditivos
