Files
BeyondCX_Insights/docs/TECH_STACK.md
sujucu70 75e7b9da3d feat: Add Streamlit dashboard with Blueprint compliance (v2.1.0)
Dashboard Features:
- 8 navigation sections: Overview, Outcomes, Poor CX, FCR, Churn, Agent, Call Explorer, Export
- Beyond Brand Identity styling (colors #6D84E3, Outfit font)
- RCA Sankey diagram (Driver → Outcome → Churn Risk flow)
- Correlation heatmaps (driver co-occurrence, driver-outcome)
- Outcome Deep Dive (root causes, correlation, duration analysis)
- Export functionality (Excel, HTML, JSON)

Blueprint Compliance:
- FCR: 4 categories (Primera Llamada/Rellamada × Sin/Con Riesgo de Fuga)
- Churn: Binary view (Sin Riesgo de Fuga / En Riesgo de Fuga)
- Agent: Talento Para Replicar / Oportunidades de Mejora
- Fixed FCR rate calculation (only FIRST_CALL counts as success)

Technical:
- Streamlit + Plotly for interactive visualizations
- Light theme configuration (.streamlit/config.toml)
- Fixed Plotly colorbar titlefont deprecation

Documentation:
- Updated PROJECT_CONTEXT.md, TODO.md, CHANGELOG.md
- Added 4 new technical decisions (TD-014 to TD-017)
- Created TROUBLESHOOTING.md with 10 common issues

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 16:27:30 +01:00

21 KiB
Raw Permalink Blame History

CXInsights - Stack Tecnológico

Resumen de Decisiones

Componente Elección Alternativas Soportadas
STT (Speech-to-Text) AssemblyAI (default) Whisper, Google STT, AWS Transcribe (via adapter)
LLM OpenAI GPT-4o-mini Claude 3.5 Sonnet (fallback)
Data Processing pandas + DuckDB -
Visualization Streamlit (internal dashboard) -
PDF Generation ReportLab -
Config Management Pydantic Settings -
PII Handling Presidio (opcional) + redaction pre-LLM -

1. Speech-to-Text: Arquitectura con Adapter

Decisión: AssemblyAI (default) + alternativas via STT Provider Adapter

El sistema usa una interfaz abstracta Transcriber que permite cambiar de proveedor sin modificar el código del pipeline.

┌─────────────────────────────────────────────────────────────────┐
│                    STT PROVIDER ADAPTER                         │
├─────────────────────────────────────────────────────────────────┤
│  Interface: Transcriber                                         │
│  └─ transcribe(audio) → TranscriptContract                     │
│                                                                 │
│  Implementations:                                               │
│  ├─ AssemblyAITranscriber (DEFAULT - mejor calidad español)    │
│  ├─ WhisperTranscriber (local, offline, $0)                    │
│  ├─ GoogleSTTTranscriber (alternativa cloud)                   │
│  └─ AWSTranscribeTranscriber (alternativa cloud)               │
│                                                                 │
│  Config: STT_PROVIDER=assemblyai|whisper|google|aws            │
└─────────────────────────────────────────────────────────────────┘

Comparativa de Proveedores

Criterio AssemblyAI Whisper (local) Google STT AWS Transcribe
Calidad español
Speaker diarization Incluido Requiere pyannote Incluido Incluido
Coste/minuto $0.015 $0 (GPU local) $0.016 $0.015
Setup complexity Bajo (API key) Alto (GPU, modelos) Medio Medio
Batch processing Async nativo Manual
Latencia ~0.3x realtime ~1x realtime ~0.2x realtime ~0.3x realtime

Por qué AssemblyAI como Default

  1. Mejor modelo para español: AssemblyAI Best tiene excelente rendimiento en español latinoamericano y castellano
  2. Speaker diarization incluido: Crítico para separar agente de cliente sin código adicional
  3. API simple: SDK Python bien documentado, async nativo
  4. Batch processing: Configurable concurrency, poll por resultados
  5. Sin infraestructura: No necesitas GPU ni mantener modelos

Cuándo usar alternativas

Alternativa Usar cuando...
Whisper local Presupuesto $0, tienes GPU (RTX 3080+), datos muy sensibles (offline)
Google STT Ya usas GCP, necesitas latencia mínima
AWS Transcribe Ya usas AWS, integración con S3

Estimación de Costes STT (AHT = 7 min)

AssemblyAI pricing: $0.015/minuto

5,000 llamadas × 7 min = 35,000 min
├─ Estimación baja (sin retries):  $525
├─ Estimación media:               $550
└─ Estimación alta (+10% retries): $580

20,000 llamadas × 7 min = 140,000 min
├─ Estimación baja:  $2,100
├─ Estimación media: $2,200
└─ Estimación alta:  $2,400

RANGO TOTAL STT:
├─ 5K calls:  $525 - $580
└─ 20K calls: $2,100 - $2,400

2. LLM: OpenAI GPT-4o-mini

Decisión: GPT-4o-mini (primary) + Claude 3.5 Sonnet (fallback)

Comparativa

Criterio GPT-4o-mini GPT-4o Claude 3.5 Sonnet
Coste input $0.15/1M tokens $2.50/1M tokens $3.00/1M tokens
Coste output $0.60/1M tokens $10.00/1M tokens $15.00/1M tokens
Calidad español
JSON structured Excelente Excelente Muy bueno
Context window 128K 128K 200K
Rate limits Depende del tier Depende del tier Depende del tier

Rate Limits y Throttling

Los rate limits dependen del tier de tu cuenta OpenAI:

Tier RPM (requests/min) TPM (tokens/min)
Tier 1 (free) 500 200K
Tier 2 5,000 2M
Tier 3 5,000 4M
Tier 4+ 10,000 10M

Requisitos obligatorios en el código:

  • Implementar throttling con tasa configurable (LLM_REQUESTS_PER_MINUTE)
  • Exponential backoff en errores 429 (rate limit exceeded)
  • Retry con jitter para evitar thundering herd
  • Logging de rate limit warnings
# Configuración recomendada (conservadora)
LLM_REQUESTS_PER_MINUTE=300  # Empezar bajo, escalar según tier
LLM_BACKOFF_BASE=2.0         # Segundos base para backoff
LLM_BACKOFF_MAX=60.0         # Máximo backoff
LLM_MAX_RETRIES=5

Estimación de Costes LLM por Llamada

IMPORTANTE: Estos cálculos asumen compresión previa del transcript (Module 2).

Escenario A: Con compresión (RECOMENDADO)

Transcript comprimido: ~1,200-1,800 tokens input
Prompt template: ~400-600 tokens
Output esperado: ~250-400 tokens

Total por llamada (comprimido):
├─ Input: ~2,000 tokens × $0.15/1M = $0.0003
├─ Output: ~350 tokens × $0.60/1M = $0.0002
└─ Total: $0.0004 - $0.0006 por llamada

RANGO (5K calls): $2 - $3
RANGO (20K calls): $8 - $12

Escenario B: Sin compresión (full transcript)

Transcript completo: ~4,000-8,000 tokens input (x3-x6)
Prompt template: ~400-600 tokens
Output esperado: ~250-400 tokens

Total por llamada (full transcript):
├─ Input: ~6,000 tokens × $0.15/1M = $0.0009
├─ Output: ~350 tokens × $0.60/1M = $0.0002
└─ Total: $0.0010 - $0.0020 por llamada

RANGO (5K calls): $5 - $10
RANGO (20K calls): $20 - $40

⚠️ RECOMENDACIÓN: Siempre usar compresión para reducir costes 3-6x

Por qué GPT-4o-mini

  1. Coste-efectividad: 17x más barato que GPT-4o, calidad suficiente para clasificación
  2. Structured outputs: JSON mode nativo, reduce errores de parsing
  3. Consistencia: Respuestas muy consistentes con prompts bien diseñados

Cuándo escalar a GPT-4o

  • Análisis que requiera razonamiento complejo
  • Casos edge con transcripciones ambiguas
  • Síntesis final de RCA trees (pocas llamadas, coste marginal)

Claude 3.5 Sonnet como fallback

Usar cuando:

  • OpenAI tiene downtime
  • Necesitas segunda opinión en casos difíciles
  • Contexto muy largo (>100K tokens)

3. Data Processing: pandas + DuckDB

Decisión: pandas (manipulación) + DuckDB (queries analíticas)

Por qué esta combinación

Componente Uso Justificación
pandas Load/transform JSON, merge data Estándar de facto, excelente para datos semi-estructurados
DuckDB Queries SQL sobre datos, aggregations SQL analítico sin servidor, integra con pandas

Por qué NO Polars

  • Polars es más rápido, pero pandas es suficiente para 20K filas
  • Mejor ecosistema y documentación
  • Equipo probablemente ya conoce pandas

Por qué NO SQLite/PostgreSQL

  • DuckDB es columnar, optimizado para analytics
  • No requiere servidor ni conexión
  • Syntax SQL estándar
  • Lee/escribe parquet nativamente

Ejemplo de uso

import pandas as pd
import duckdb

# Cargar todos los labels
labels = pd.read_json("data/processed/*.json")  # via glob

# Query analítico con DuckDB
result = duckdb.sql("""
    SELECT
        lost_sale_driver,
        COUNT(*) as count,
        COUNT(*) * 100.0 / SUM(COUNT(*)) OVER () as pct
    FROM labels
    WHERE outcome = 'no_sale'
    GROUP BY lost_sale_driver
    ORDER BY count DESC
""").df()

4. Visualization: Streamlit

Decisión: Streamlit (dashboard interno)

Alcance y Limitaciones

┌─────────────────────────────────────────────────────────────────┐
│                    STREAMLIT - ALCANCE                          │
├─────────────────────────────────────────────────────────────────┤
│  ✅ ES:                                                         │
│  ├─ Dashboard interno para equipo de análisis                  │
│  ├─ Visualización de resultados de batch procesado             │
│  ├─ Drill-down por llamada individual                          │
│  └─ Exportación a PDF/Excel                                    │
│                                                                 │
│  ❌ NO ES:                                                      │
│  ├─ Portal enterprise multi-tenant                             │
│  ├─ Aplicación de producción con SLA                          │
│  ├─ Dashboard para >50 usuarios concurrentes                   │
│  └─ Sistema con autenticación compleja                         │
└─────────────────────────────────────────────────────────────────┘

Comparativa

Criterio Streamlit Plotly Dash FastAPI+React
Setup time 1 hora 4 horas 2-3 días
Interactividad
Aprendizaje Bajo Medio Alto
Customización Limitada Alta Total
Usuarios concurrentes ~10-50 ~50-100 Sin límite

Deploy

┌─────────────────────────────────────────────────────────────────┐
│                    OPCIONES DE DEPLOY                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  OPCIÓN 1: Local (desarrollo/análisis personal)                │
│  $ streamlit run src/visualization/dashboard.py                │
│  → http://localhost:8501                                       │
│                                                                 │
│  OPCIÓN 2: VM/Servidor interno (equipo pequeño)                │
│  $ streamlit run dashboard.py --server.port 8501               │
│  → Sin auth, acceso via VPN/red interna                        │
│                                                                 │
│  OPCIÓN 3: Con proxy + auth básica (recomendado producción)    │
│  Nginx/Caddy → Basic Auth → Streamlit                          │
│  → Auth configurable via .htpasswd o OAuth proxy               │
│                                                                 │
│  OPCIÓN 4: Streamlit Cloud (demos/POC)                         │
│  → Gratis, pero datos públicos (no para producción)            │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Configuración de Auth (opcional)

# nginx.conf - Basic Auth para Streamlit
server {
    listen 443 ssl;
    server_name dashboard.internal.company.com;

    auth_basic "CXInsights Dashboard";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
        proxy_pass http://localhost:8501;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Alternativa futura

Si necesitas dashboard enterprise:

  • Migrar a FastAPI backend + React frontend
  • Reusar lógica de aggregation
  • Añadir auth, multi-tenant, RBAC

5. PII Handling

Decisión: Redaction pre-LLM obligatoria + retención controlada

┌─────────────────────────────────────────────────────────────────┐
│                    PII HANDLING STRATEGY                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  PRINCIPIO: Minimizar PII enviado a APIs externas              │
│                                                                 │
│  1. REDACTION PRE-LLM (obligatorio)                            │
│     ├─ Nombres → [NOMBRE]                                      │
│     ├─ Teléfonos → [TELEFONO]                                  │
│     ├─ Emails → [EMAIL]                                        │
│     ├─ DNI/NIE → [DOCUMENTO]                                   │
│     ├─ Tarjetas → [TARJETA]                                    │
│     └─ Direcciones → [DIRECCION]                               │
│                                                                 │
│  2. RETENCIÓN POR BATCH                                        │
│     ├─ Transcripts raw: borrar tras 30 días o fin de proyecto │
│     ├─ Transcripts compressed: borrar tras procesamiento       │
│     ├─ Labels (sin PII): retener para análisis                │
│     └─ Aggregated stats: retener indefinidamente              │
│                                                                 │
│  3. LOGS                                                        │
│     ├─ NUNCA loguear transcript completo                       │
│     ├─ Solo loguear: call_id, timestamps, errores             │
│     └─ Logs en volumen separado, rotación 7 días              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Implementación

# Opción 1: Regex básico (mínimo viable)
REDACTION_PATTERNS = {
    r'\b\d{8,9}[A-Z]?\b': '[DOCUMENTO]',           # DNI/NIE
    r'\b\d{9}\b': '[TELEFONO]',                     # Teléfono
    r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b': '[EMAIL]',
    r'\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b': '[TARJETA]',
}

# Opción 2: Presidio (recomendado para producción)
# Más preciso, soporta español, detecta contexto
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine

6. Dependencias Python

Core Dependencies

[project]
dependencies = [
    # STT
    "assemblyai>=0.26.0",

    # LLM
    "openai>=1.40.0",
    "anthropic>=0.34.0",  # fallback

    # Data Processing
    "pandas>=2.2.0",
    "duckdb>=1.0.0",
    "pydantic>=2.8.0",

    # Visualization
    "streamlit>=1.38.0",
    "plotly>=5.24.0",
    "matplotlib>=3.9.0",

    # PDF/Excel Export
    "reportlab>=4.2.0",
    "openpyxl>=3.1.0",
    "xlsxwriter>=3.2.0",

    # Config & Utils
    "pydantic-settings>=2.4.0",
    "python-dotenv>=1.0.0",
    "pyyaml>=6.0.0",
    "tqdm>=4.66.0",
    "tenacity>=8.5.0",  # retry logic

    # JSON (performance + validation)
    "orjson>=3.10.0",             # Fast JSON serialization
    "jsonschema>=4.23.0",         # Schema validation

    # Async
    "aiofiles>=24.1.0",
    "httpx>=0.27.0",
]

[project.optional-dependencies]
# PII detection (opcional pero recomendado)
pii = [
    "presidio-analyzer>=2.2.0",
    "presidio-anonymizer>=2.2.0",
    "spacy>=3.7.0",
    "es-core-news-sm @ https://github.com/explosion/spacy-models/releases/download/es_core_news_sm-3.7.0/es_core_news_sm-3.7.0-py3-none-any.whl",
]

dev = [
    "pytest>=8.3.0",
    "pytest-asyncio>=0.24.0",
    "pytest-cov>=5.0.0",
    "ruff>=0.6.0",
    "mypy>=1.11.0",
]

Justificación de cada dependencia

Dependencia Propósito Por qué esta
assemblyai SDK oficial STT Mejor integración, async nativo
openai SDK oficial GPT Structured outputs, streaming
anthropic SDK oficial Claude Fallback LLM
pandas Manipulación datos Estándar industria
duckdb Queries SQL Analytics sin servidor
pydantic Validación schemas Type safety, JSON parsing
streamlit Dashboard Rápido, Python-only
plotly Gráficos interactivos Mejor para web
matplotlib Gráficos estáticos Export PNG
reportlab PDF generation Maduro, flexible
openpyxl Excel read/write Pandas integration
pydantic-settings Config management .env + validation
tqdm Progress bars UX en CLI
tenacity Retry logic Rate limits, API errors
orjson JSON serialization 10x más rápido que json stdlib
jsonschema Schema validation Validar outputs LLM
httpx HTTP client async Mejor que requests
presidio-* PII detection Precisión en español, contexto

7. Versiones de Python

Decisión: Python 3.11+

Justificación

  • 3.11: 10-60% más rápido que 3.10
  • 3.11: Better error messages
  • 3.12: Algunas libs aún no compatibles
  • Match pattern (3.10+) útil para parsing

8. Consideraciones de Seguridad

API Keys

# .env (NUNCA en git)
ASSEMBLYAI_API_KEY=xxx
OPENAI_API_KEY=sk-xxx
ANTHROPIC_API_KEY=sk-ant-xxx  # opcional

Rate Limiting (implementación obligatoria)

# src/inference/client.py
from tenacity import retry, wait_exponential, stop_after_attempt

@retry(
    wait=wait_exponential(multiplier=2, min=1, max=60),
    stop=stop_after_attempt(5),
    retry=retry_if_exception_type(RateLimitError)
)
async def call_llm(prompt: str) -> str:
    # Throttle requests
    await self.rate_limiter.acquire()
    # ... llamada a API

Checklist de seguridad

  • API keys en .env, nunca en código
  • .env en .gitignore
  • PII redactado antes de LLM
  • Logs sin transcripts completos
  • Rate limiting implementado
  • Backoff exponencial en errores 429

9. Alternativas Descartadas

Whisper Local

  • Pro: Gratis, offline, datos sensibles
  • Contra: Necesita GPU, sin diarization nativo, más lento
  • Decisión: Soportado via adapter, no es default

LangChain

  • Pro: Abstracciones útiles, chains
  • Contra: Overhead innecesario para este caso, complejidad
  • Decisión: Llamadas directas a SDK son suficientes

PostgreSQL/MySQL

  • Pro: Persistencia, queries complejas
  • Contra: Requiere servidor, overkill para batch
  • Decisión: DuckDB + archivos JSON/parquet

Celery/Redis

  • Pro: Job queue distribuida
  • Contra: Infraestructura adicional
  • Decisión: asyncio + checkpointing es suficiente

10. Resumen de Costes

Parámetros base

  • AHT (Average Handle Time): 7 minutos
  • Compresión de transcript: Asumida (reducción ~60% tokens)

Por 5,000 llamadas

Servicio Cálculo Rango
AssemblyAI STT 35,000 min × $0.015/min $525 - $580
OpenAI LLM (comprimido) 5,000 × $0.0005 $2 - $3
OpenAI RCA synthesis ~10 calls × $0.02 $0.20
TOTAL $530 - $590

Por 20,000 llamadas

Servicio Cálculo Rango
AssemblyAI STT 140,000 min × $0.015/min $2,100 - $2,400
OpenAI LLM (comprimido) 20,000 × $0.0005 $8 - $12
OpenAI RCA synthesis ~10 calls × $0.02 $0.20
TOTAL $2,110 - $2,420

Sin compresión (escenario pesimista)

Volumen STT LLM (full transcript) Total
5,000 calls $525-580 $5-10 $530 - $590
20,000 calls $2,100-2,400 $20-40 $2,120 - $2,440

Coste de infraestructura

Opción Coste
Local (tu máquina) $0
VM cloud (procesamiento) $20-50/mes
Streamlit Cloud (demos) Gratis
VM + Nginx (producción) $30-80/mes