Dossier Técnico

ECCO
Estación de Captura
y Conocimiento Oficial

Sistema integral de transcripción, análisis y gestión de sesiones del Concejo Municipal
Versión
1.0.0
Fecha
Mayo 2025
Desarrollado por
PINGS — DSinf
Cliente de referencia
Concejo de Medellín
Clasificación
Técnico Interno
Stack principal
Python 3.11 · SQLite · Ollama · CUDA
01

Introducción

ECCO (Estación de Captura y Conocimiento Oficial) es una plataforma de software desarrollada íntegramente en Python por PINGS, orientada a la gestión integral del ciclo documental de sesiones del Concejo Municipal. El sistema automatiza la cadena completa: grabación o importación de audio → transcripción ASR → diarización de hablantes → análisis semántico con LLM → generación de actas oficiales → búsqueda de conocimiento institucional.

El diseño prioriza la soberanía tecnológica: todos los modelos de IA corren localmente (Ollama, faster-whisper, pyannote), sin dependencia de APIs externas de pago ni envío de datos sensibles a terceros.

1.1 Objetivos del sistema

Automatización

Reducir de 8–12 horas (transcripción manual) a menos de 30 minutos el tiempo de elaboración de un acta de sesión estándar de 2 horas.

Trazabilidad

Registrar cada intervención, voto y asistencia vinculada al concejal correspondiente, con evidencia de audio alineada al texto.

Acceso al conocimiento

Motor de búsqueda híbrido (FTS5 + vectorial) sobre todo el archivo histórico de sesiones y la base legal municipal.

Soberanía de datos

Operación 100% offline sobre hardware institucional. Sin dependencias de servicios cloud. Datos almacenados localmente en SQLite + ChromaDB.

1.2 Alcance funcional

MóduloFuncionalidadEstado
Grabación en vivoCaptura multi-canal, pausa/resume, nivel dB en tiempo realImplementado
Importación de audioMP3, WAV, M4A, OGG, FLAC, AACImplementado
Transcripción ASRWhisper large-v3 vía faster-whisper, CUDA int8Implementado
Diarizaciónpyannote/speaker-diarization-3.1Implementado
Análisis LLMTemas, resumen, asistencia, votaciones, sentimientoImplementado
Generación de actaBorrador completo con estilo institucionalImplementado
ExportaciónDOCX (python-docx) + PDF (reportlab)Implementado
Búsqueda híbridaFTS5 + ChromaDB + rerankingImplementado
Base legal RAGLegislación municipal con RAG contextualImplementado
Perfiles concejalesCRUD, estadísticas, fotosImplementado
Dashboard analyticsKPIs, gráficos Plotly interactivosImplementado
OCR de PDFspytesseract para documentos escaneadosImplementado

1.3 Restricciones y supuestos de diseño

Restricciones conocidas en v1.0
  • Requiere GPU NVIDIA con soporte CUDA 12.x para rendimiento óptimo. Sin GPU, el pipeline corre en CPU con tiempos 5–10× mayores.
  • La diarización con pyannote requiere token de aceptación de Hugging Face (licencia de modelos).
  • La base de datos SQLite no está diseñada para concurrencia multi-proceso. Un único proceso Streamlit accede a la BD en cada instancia.
  • El modelo Ollama (qwen2.5:7b) requiere ~6 GB de RAM disponible.
  • El sistema no provee autenticación ni control de acceso en v1.0; se asume red interna institucional.
02

Arquitectura General

ECCO sigue una arquitectura de capas desacopladas: la capa de presentación (Streamlit) invoca servicios de dominio (src/) que a su vez acceden a los datos (SQLite + ChromaDB). El pipeline de procesamiento es asíncrono: se lanza en hilo separado mientras la UI muestra progreso.

┌─────────────────────────────────────────────────────────────────────┐ │ ECCO — ARQUITECTURA DE CAPAS │ └─────────────────────────────────────────────────────────────────────┘ ┌── CAPA 1: PRESENTACIÓN (Streamlit Multipage) ────────────────────────┐ app.py pages/02_Nueva_Sesion.py pages/03_Sesiones.py Dashboard/KPIs Grabación + Importación Lista con filtros pages/04_Transcripcion.py pages/05_Actas.py pages/06_Busqueda Visor+Player+Edición Gen. IA+DOCX/PDF Híbrida FTS5+Vec. pages/07_Concejales.py pages/10_Base_Legal.py pages/11_Estadist. Perfiles CRUD+Stats Legislación+RAG Plotly Analytics └──────────────────────────────────┬──────────────────────────────────┘ │ invoca ┌── CAPA 2: SERVICIOS DE DOMINIO (src/) ───────────────────────────────┐ src/transcription/ src/intelligence/ pipeline.py (orquestador) ollama_client.py ├─ Audio → Normalizar summarizer.py ├─ Denoising (DeepFilter) topic_extractor.py ├─ Chunking 30min voting_detector.py ├─ Whisper large-v3 CUDA attendance_parser.py ├─ pyannote diarización sentiment_analyzer.py └─ Alinear + Guardar BD acta_generator.py src/search/ src/audio/ embedder.py (ChromaDB) recorder.py fts_engine.py (SQLite FTS5) importer.py hybrid_search.py normalizer.py rag_engine.py denoiser.py device_manager.py src/documents/ acta_docx.py acta_pdf.py ocr_importer.py └──────────────────────────────────┬──────────────────────────────────┘ │ lee/escribe ┌── CAPA 3: PERSISTENCIA ──────────────────────────────────────────────┐ SQLite 3 (ecco.db) ChromaDB (chroma_store/) 12 tablas relacionales Colección "intervenciones" FTS5 en intervenciones Colección "legislacion" FTS5 en legislacion Embeddings nomic-embed-text Sistema de archivos data/audios/ data/exportaciones/ data/fotos/ logs/ └──────────────────────────────────┬──────────────────────────────────┘ │ inferencia ┌── CAPA 4: MODELOS IA (locales) ──────────────────────────────────────┐ Ollama (localhost:11434) faster-whisper large-v3 (CUDA) ├─ qwen2.5:7b (LLM análisis) pyannote speaker-diarization-3.1 └─ nomic-embed-text (embeddings) DeepFilterNet (denoising) pysentimiento/robertuito whisperx (alineación) └─────────────────────────────────────────────────────────────────────┘

Flujo de datos principal

El flujo unidireccional garantiza que ninguna capa superior escriba directamente en la capa de persistencia obviando los servicios de dominio. La UI solo llama funciones de src/; los servicios de dominio ejecutan toda la lógica de negocio y acceden a la BD a través de un módulo centralizado src/database/db.py.

Diseño sin ORM El acceso a SQLite usa el módulo nativo sqlite3 con queries parametrizadas, sin ORM (SQLAlchemy, etc.). Esto mantiene el código simple, portable y permite aprovechar características propias de SQLite (FTS5, window functions, JSON1) sin capas de abstracción.
03

Componentes Detallados

3.1 Capa UI — Streamlit Multipage

La interfaz se implementa como una aplicación Streamlit multipage. El archivo raíz app.py define el layout global (sidebar, tema, KPIs del dashboard principal). Cada página en pages/ es un módulo independiente que se carga bajo demanda.

Dashboard
app.py
  • KPIs: sesiones totales, horas transcritas, concejales activos
  • Gráfico de sesiones por mes (Plotly)
  • Últimas sesiones procesadas
  • Estado del sistema (Ollama, GPU, espacio)
  • Sidebar de navegación global
Nueva Sesión
pages/02_Nueva_Sesion.py
  • Selector de dispositivo de audio (sounddevice)
  • VU-meter en tiempo real (nivel dB)
  • Grabación con pausa / resume / detener
  • Importación drag-and-drop (MP3/WAV/M4A/OGG/FLAC/AAC)
  • Formulario de metadatos (fecha, tipo, orden del día)
  • Lanzamiento del pipeline con barra de progreso
Sesiones
pages/03_Sesiones.py
  • Listado paginado de todas las sesiones
  • Filtros: fecha, tipo, estado, concejal
  • Indicador de estado del pipeline por sesión
  • Acceso rápido a transcripción y acta
  • Eliminar sesión con confirmación
Transcripción
pages/04_Transcripcion.py
  • Visor segmentado por hablante y timestamp
  • Player de audio HTML5 sincronizado
  • Edición inline de texto y asignación de hablante
  • Resumen automático colapsable
  • Lista de temas identificados
  • Export de transcripción en TXT/JSON
Actas
pages/05_Actas.py
  • Generación bajo demanda con LLM
  • Editor de texto enriquecido integrado
  • Secciones editables: apertura, asistencia, orden del día, votaciones, cierre
  • Export DOCX con estilos institucionales
  • Export PDF con reportlab
  • Historial de versiones del acta
Búsqueda
pages/06_Busqueda.py
  • Barra de búsqueda unificada
  • Toggle FTS / Vectorial / Híbrida
  • Resultados con snippet de contexto
  • Filtros por fecha, concejal, tipo
  • Link directo a segmento de sesión
Concejales
pages/07_Concejales.py
  • Grid de perfiles con foto
  • CRUD completo (crear/editar/eliminar)
  • Stats por concejal: asistencia %, votaciones, palabras
  • Timeline de participación
  • Seed de 21 concejales 2024-2027
Base Legal
pages/10_Base_Legal.py
  • Importación de documentos legales (PDF/DOCX)
  • OCR para PDFs escaneados
  • RAG: preguntas en lenguaje natural
  • Citas con referencia al artículo/documento
  • FTS5 sobre texto legal completo
Estadísticas
pages/11_Estadisticas.py
  • Asistencia histórica por concejal (heatmap)
  • Distribución de votaciones (a favor/contra/abstención)
  • Temas más frecuentes (wordcloud)
  • Duración media de sesiones
  • Sentimiento promedio por sesión
  • Export de datos a CSV

3.2 Módulo de Inteligencia (src/intelligence/)

Ollama Client
src/intelligence/ollama_client.py
  • Cliente HTTP para API REST de Ollama (localhost:11434)
  • Métodos: chat(), embed(), stream()
  • Retry automático con backoff exponencial
  • Manejo de timeout configurable
  • Verificación de disponibilidad del servicio
Summarizer
src/intelligence/summarizer.py
  • Estrategia map-reduce para sesiones largas
  • Chunk de ~2000 tokens con overlap de 200
  • Fase map: resumen de cada chunk
  • Fase reduce: síntesis final coherente
  • Prompt especializado en lenguaje institucional
Topic Extractor
src/intelligence/topic_extractor.py
  • Extracción de temas con LLM (qwen2.5:7b)
  • Salida estructurada JSON: nombre, descripción, segmentos
  • Deduplicación semántica de temas
  • Vinculación tema ↔ sesión en BD
Voting Detector
src/intelligence/voting_detector.py
  • Detección de frases de votación (regex + LLM)
  • Extracción de resultado: a favor / en contra / abstención
  • Identificación del asunto votado
  • Parsing de votos individuales cuando se enuncian
  • Confianza del resultado (0–1)
Attendance Parser
src/intelligence/attendance_parser.py
  • Detecta llamado a lista en la transcripción
  • Fuzzy-match nombre hablante → concejal en BD
  • Infiere presente/ausente/excusado
  • Umbral de confianza configurable
Sentiment Analyzer
src/intelligence/sentiment_analyzer.py
  • Modelo pysentimiento/robertuito (español)
  • Sentimiento por intervención: POS / NEU / NEG
  • Agregación por sesión y por concejal
  • Scores guardados en tabla intervenciones
Acta Generator
src/intelligence/acta_generator.py
  • Genera acta oficial completa desde datos de BD
  • Secciones: encabezado, asistencia, desarrollo, votaciones, cierre
  • Prompt con plantilla institucional parametrizable
  • Modo borrador (automático) y modo revisado (manual)
  • Retorno de HTML estructurado para editor
Embedder
src/search/embedder.py
  • ChromaDB como vector store persistente
  • Modelo nomic-embed-text vía Ollama
  • Chunking con overlap (500 chars / 50 overlap)
  • Metadatos: sesion_id, concejal_id, timestamp, tipo
  • Indexación incremental (solo nuevos documentos)
FTS Engine
src/search/fts_engine.py
  • SQLite FTS5 con tokenize=unicode61
  • Índices virtuales: intervenciones_fts, legislacion_fts
  • BM25 automático de FTS5
  • Highlight y snippet integrados
  • Búsqueda por frase exacta y booleana
Hybrid Search
src/search/hybrid_search.py
  • Fusión: 40% FTS5 + 60% vectorial
  • Normalización de scores antes de fusión
  • Reranking por relevancia combinada
  • Deduplicación de resultados solapados
  • Top-K configurable (default: 20)
RAG Engine
src/search/rag_engine.py
  • Retrieval-Augmented Generation para base legal
  • Top-5 fragmentos relevantes como contexto
  • Prompt con instrucción de citación
  • Respuesta con referencias a artículo y documento
  • Modo sin contexto (fallback a LLM puro)

3.4 Módulo de Audio (src/audio/)

Recorder
src/audio/recorder.py
  • sounddevice con callback de buffer
  • Threading: hilo de grabación + hilo de UI
  • Pausa/resume sin cortar el archivo
  • Nivel RMS en dB actualizado cada 100ms
  • Guardado a WAV 48kHz 16-bit mono
Importer
src/audio/importer.py
  • Detecta formato con python-magic
  • Convierte a WAV 48kHz mono vía pydub
  • Validación de duración mínima (30s)
  • Copia a data/audios/ con UUID
Normalizer
src/audio/normalizer.py
  • pydub: resample a 48kHz mono
  • Normalización de volumen a -20 dBFS
  • Recorte de silencios inicial y final
  • Export a WAV antes de transcripción
Denoiser
src/audio/denoiser.py
  • DeepFilterNet v3 para supresión de ruido
  • Procesa en segmentos de 10s con overlap
  • Opcional (configurable en config.yaml)
  • Mejora WER hasta 15% en grabaciones ruidosas
Device Manager
src/audio/device_manager.py
  • Lista todos los dispositivos de audio del sistema
  • Detecta dispositivos de loopback (captura de sistema)
  • Prueba de grabación de 2s por dispositivo
  • Guarda preferencia en config

3.5 Módulo de Documentos (src/documents/)

Acta DOCX
src/documents/acta_docx.py
  • python-docx con estilos institucionales
  • Plantilla base con logo, encabezado, pie
  • Estilos: Título, Sección, Cuerpo, Tabla
  • Tabla de asistencia formateada
  • Tabla de votaciones con colores
  • Numeración automática de páginas
Acta PDF
src/documents/acta_pdf.py
  • reportlab con platypus (flowables)
  • Estilos definidos vía StyleSheet
  • Header/footer por página
  • Tabla de contenidos automática
  • Firma digital visual (placeholder)
OCR Importer
src/documents/ocr_importer.py
  • pytesseract + Tesseract 5 (lang=spa)
  • pdf2image para render de páginas
  • Preprocesamiento: binarización, deskew
  • Extrae texto estructurado por párrafo
  • Inserta en tabla legislacion
04

Pipeline de Procesamiento

El pipeline principal vive en src/transcription/pipeline.py y es el corazón del sistema. Transforma un archivo de audio crudo en datos estructurados listos para acta. Se ejecuta en un hilo de fondo (threading.Thread) para no bloquear la UI de Streamlit. El estado de progreso se persiste en la columna sesiones.estado_pipeline.

Tiempos estimados — Hardware de referencia (GTX 1660, 6GB VRAM, 8 cores, 16GB RAM) Sesión de 1 hora de audio: 15–25 minutos de procesamiento total.
~10s
1. Carga y validación del audio
Verifica formato, duración mínima (30s), integridad del archivo. Copia a data/audios/<uuid>.wav si es importación.
~30s / hora
2. Normalización de audio (pydub)
Resample a 48kHz mono 16-bit. Normalización de volumen a -20 dBFS. Recorte de silencios >2s al inicio y fin.
~2min / hora
3. Denoising — DeepFilterNet v3
Supresión de ruido de fondo (ventiladores, AC, eco). Opcional según config.yaml:pipeline.denoising_enabled. Procesa en ventanas de 10s con overlap de 1s.
<1s
4. Chunking en segmentos de 30 min
Divide el audio en chunks de 1800s con overlap de 10s. Garantiza que Whisper no exceeda el límite de contexto y que VRAM no se sature.
5–8 min / hora (GPU)
5. Transcripción — Whisper large-v3 (faster-whisper, CUDA int8)
ASR principal. compute_type=int8 cuantización para GTX 1660. language=es hardcoded. Produce segmentos con timestamps inicio/fin y score de confianza. VRAM descargada al finalizar cada chunk.
3–5 min / hora
6. Diarización — pyannote/speaker-diarization-3.1
Identifica segmentos por hablante (SPEAKER_00, SPEAKER_01…). Requiere token HuggingFace. Número de hablantes puede fijarse en config o detectarse automáticamente (min 2, max 40).
~30s / hora
7. Alineación Whisper ↔ Diarización (whisperx)
Asocia cada segmento de texto con el hablante dominante de pyannote. Criterio: hablante con mayor overlap temporal con el segmento ASR.
~5s
8. Guardado en base de datos
Inserta intervenciones en SQLite. Actualiza FTS5 (trigger automático). Guarda metadatos de la sesión.
2–4 min / hora
9. Generación de embeddings (nomic-embed-text → ChromaDB)
Chunking semántico de las intervenciones. Embedding vía Ollama nomic-embed-text. Upsert en colección ChromaDB con metadatos de sesión y concejal.
1–2 min / hora
10. Análisis LLM (qwen2.5:7b via Ollama)
Ejecuta 5 tareas en secuencia: (a) extracción de temas, (b) resumen map-reduce, (c) parsing de asistencia, (d) detección de votaciones, (e) análisis de sentimiento (robertuito). Resultados guardados en tablas correspondientes.
0s
11. Estado final: acta_borrador
La sesión queda en estado acta_borrador. El usuario puede revisar transcripción, editar y exportar. El acta completa se genera bajo demanda desde la página de Actas.

Estados del pipeline

EstadoDescripciónPantalla
pendienteAudio cargado, pipeline no iniciadoBotón "Procesar"
procesandoPipeline en ejecución (hilo activo)Barra de progreso + log
transcritoWhisper + diarización completadosTranscripción disponible
analizadoLLM completó análisis completoTemas, resumen, asistencia
acta_borradorListo para generar/editar actaBotón "Generar Acta"
acta_aprobadaActa revisada y aprobadaBadge verde, export habilitado
errorFallo en algún paso del pipelineLog de error, botón retry
05

Modelo de Datos

La base de datos es un archivo SQLite 3 (data/ecco.db) con 12 tablas. Se inicializa con scripts/03_inicializar_bd.py que crea el esquema, los índices FTS5 y siembra los datos de concejales.

concejales (id, nombre, partido, comision, foto_path, periodo, activo) │ ├──◄── asistencia (id, sesion_id, concejal_id, estado, justificacion) │ ├──◄── intervenciones (id, sesion_id, concejal_id, inicio_seg, fin_seg, │ texto, confianza, sentimiento, speaker_label) │ └── intervenciones_fts [FTS5 virtual] │ └──◄── votos_individuales (id, votacion_id, concejal_id, voto) sesiones (id, fecha, tipo, orden_dia, audio_path, duracion_seg, estado_pipeline, resumen, created_at) │ ├──◄── asistencia ├──◄── intervenciones ├──◄── votaciones (id, sesion_id, asunto, a_favor, en_contra, │ abstencion, resultado, confianza, timestamp_seg) │ └──◄── votos_individuales ├──◄── tema_sesion (id, sesion_id, tema_id, relevancia) ├──◄── actas (id, sesion_id, version, contenido_html, estado, │ docx_path, pdf_path, created_at, updated_at) └──◄── fotos_sesion (id, sesion_id, path, descripcion, orden) temas (id, nombre, descripcion, color) └──◄── tema_sesion legislacion (id, titulo, tipo, numero, anno, texto_completo, fuente_path, created_at) └── legislacion_fts [FTS5 virtual] embeddings_index (id, coleccion, doc_id, sesion_id, concejal_id, chunk_index)

Tablas principales — descripción de campos clave

sesiones

CampoTipoDescripción
idINTEGER PKID autoincremental
fechaDATEFecha de celebración de la sesión
tipoTEXTordinaria | extraordinaria | especial | comision
orden_diaTEXTJSON array de puntos del orden del día
audio_pathTEXTRuta relativa al WAV normalizado
duracion_segREALDuración total en segundos
estado_pipelineTEXTEstado actual del pipeline (ver §4)
resumenTEXTResumen generado por LLM (markdown)
num_concejalesINTEGERAsistentes confirmados

intervenciones

CampoTipoDescripción
idINTEGER PKID autoincremental
sesion_idINTEGER FK→ sesiones.id
concejal_idINTEGER FK NULL→ concejales.id (NULL si no identificado)
speaker_labelTEXTSPEAKER_00, SPEAKER_01… (pyannote raw)
inicio_segREALTimestamp inicio en segundos
fin_segREALTimestamp fin en segundos
textoTEXTTranscripción del segmento
confianzaREALScore WER inverso de Whisper (0–1)
sentimientoTEXTPOS | NEU | NEG (robertuito)
sentimiento_scoreREALProbabilidad del sentimiento asignado

Tabla FTS5 — intervenciones_fts

-- Tabla virtual FTS5 sincronizada con intervenciones via triggers
CREATE VIRTUAL TABLE intervenciones_fts USING fts5(
    texto,
    content='intervenciones',
    content_rowid='id',
    tokenize='unicode61 remove_diacritics 1'
);

-- Trigger de sincronización al insertar
CREATE TRIGGER intervenciones_ai AFTER INSERT ON intervenciones BEGIN
  INSERT INTO intervenciones_fts(rowid, texto) VALUES(new.id, new.texto);
END;
Nota sobre embeddings_index La tabla embeddings_index actúa como índice relacional de los documentos almacenados en ChromaDB. Permite joins entre resultados vectoriales (que devuelven doc_id) y las tablas relacionales de SQLite sin tener que duplicar datos en el vector store.
06

Búsqueda Híbrida

El motor de búsqueda de ECCO combina dos paradigmas complementarios para maximizar la cobertura y relevancia de resultados sobre el corpus de sesiones del concejo.

FTS5 — Lexical (40%)

SQLite FTS5 con BM25 ranker. Ideal para búsquedas exactas: nombres propios, artículos de ley, citas textuales. Tokenizador unicode61 con remoción de diacríticos permite encontrar "sesion" al buscar "sesión".

Fortaleza: velocidad (ms), precisión léxica.
Debilidad: no entiende semántica ("aprobaron" ≠ "votaron").

Vectorial — Semántico (60%)

ChromaDB con embeddings nomic-embed-text (768 dim). Encuentra conceptualmente similar aunque las palabras sean distintas. Cosine similarity.

Fortaleza: semántica, sinónimos, paráfrasis.
Debilidad: puede traer resultados relacionados pero no relevantes para la query exacta.

Algoritmo de fusión y reranking

# src/search/hybrid_search.py — pseudocódigo simplificado

def hybrid_search(query: str, top_k: int = 20) -> list[Result]:

    # 1. FTS5 search
    fts_results = fts_engine.search(query, limit=top_k * 2)
    # Normalizar scores FTS (BM25) a [0, 1]
    fts_norm = normalize_minmax([r.score for r in fts_results])

    # 2. Vector search
    vec_results = embedder.query(query, n_results=top_k * 2)
    # Normalizar distancias coseno a similitud [0, 1]
    vec_norm = [1 - d for d in vec_results.distances]

    # 3. Fusión ponderada
    combined = {}
    for r, score in zip(fts_results, fts_norm):
        combined[r.id] = score * 0.40
    for r, score in zip(vec_results, vec_norm):
        combined[r.id] = combined.get(r.id, 0) + score * 0.60

    # 4. Deduplicar y ordenar por score combinado
    ranked = sorted(combined.items(), key=lambda x: x[1], reverse=True)

    # 5. Enriquecer con datos de BD (concejal, sesión, snippet)
    return enrich_results(ranked[:top_k])

Flujo de indexación

Chunking semántico

Las intervenciones se dividen en chunks de ~500 caracteres con overlap de 50 caracteres. Los chunks no cortan en medio de oración (se busca el último punto antes del límite).

Embedding con nomic-embed-text

Cada chunk se convierte en un vector de 768 dimensiones vía Ollama. El modelo nomic-embed-text está optimizado para recuperación de información en español.

Upsert en ChromaDB

Los vectores se almacenan en la colección intervenciones con metadatos: sesion_id, concejal_id, inicio_seg, tipo. ChromaDB persiste en disco en data/chroma_store/.

Registro en embeddings_index

Se registra en SQLite la correspondencia doc_id (ChromaDB) ↔ intervencion_id (SQLite) para poder hacer joins relacionales sobre resultados vectoriales.

RAG para Base Legal

El módulo rag_engine.py implementa Retrieval-Augmented Generation sobre la colección legislacion de ChromaDB. El flujo es:

  1. Usuario formula pregunta en lenguaje natural (ej: "¿Cuál es el quórum para sesiones extraordinarias?")
  2. Se generan embeddings de la pregunta con nomic-embed-text
  3. Se recuperan los 5 fragmentos más similares de la base legal
  4. Los fragmentos se inyectan como contexto en el prompt de qwen2.5:7b
  5. El LLM responde con cita al artículo y documento fuente
07

Requisitos de Instalación

7.1 Hardware recomendado

ComponenteMínimoRecomendado (ref.)Notas
GPUGTX 1060 6GB VRAMGTX 1660 6GB / RTX 3060CUDA 12.1+, compute cap ≥6.1
CPU4 cores / 3.0 GHz8 cores / 3.5 GHzpyannote usa CPU intensivamente
RAM12 GB16 GBOllama necesita ~6 GB para qwen2.5:7b
Almacenamiento50 GB SSD200 GB SSD NVMe1h audio ≈ 200 MB WAV; acumulación histórica
AudioMicrófono USBInterfaz de audio 4chsounddevice compatible WASAPI/ALSA

7.2 Software base (Windows)

ComponenteVersiónFuente
Windows 10/11 Pro22H2+Microsoft
Python3.11.xpython.org
CUDA Toolkit12.1+developer.nvidia.com
cuDNN8.9+developer.nvidia.com
Ollama0.3+ollama.ai
Git2.40+git-scm.com
FFmpeg6.0+ffmpeg.org (en PATH)
Tesseract OCR5.3+UB Mannheim builds
Visual C++ Build Tools2022Microsoft (para compilar deps)

7.3 Dependencias Python principales

# requirements.txt (extracto de dependencias críticas)
streamlit==1.35.0
faster-whisper==1.0.3
whisperx==3.1.5
pyannote.audio==3.1.1
torch==2.3.0+cu121
chromadb==0.5.3
deepfilternet==0.5.6
python-docx==1.1.0
reportlab==4.2.0
plotly==5.22.0
sounddevice==0.4.7
pydub==0.25.1
pysentimiento==0.7.2
pytesseract==0.3.13
pdf2image==1.17.0
requests==2.32.0
pyyaml==6.0.1
python-magic-bin==0.4.14  # Windows
Sobre PyTorch + CUDA Instalar PyTorch con índice CUDA explícito: pip install torch==2.3.0+cu121 --index-url https://download.pytorch.org/whl/cu121. La versión CPU de PyTorch NO es suficiente para el rendimiento requerido.
08

Guía de Instalación — Windows

Instalar dependencias del sistema

Instalar Python 3.11, CUDA 12.1, cuDNN 8.9, Git, FFmpeg (agregar al PATH), Tesseract 5 (con paquete de idioma spa), Visual C++ Build Tools 2022.

# Verificar CUDA disponible
nvcc --version
python -c "import torch; print(torch.cuda.is_available())"

Instalar y configurar Ollama

# Descargar instalador desde ollama.ai e instalar
# Luego descargar los modelos requeridos:
ollama pull qwen2.5:7b
ollama pull nomic-embed-text
# Verificar que el servicio corre en localhost:11434
ollama list

Clonar el repositorio ECCO

git clone https://github.com/PINGS-DSinf/ECCO.git
cd ECCO

Crear entorno virtual e instalar dependencias

python -m venv .venv
.venv\Scripts\activate

# PyTorch con CUDA primero
pip install torch==2.3.0+cu121 torchaudio==2.3.0+cu121 ^
    --index-url https://download.pytorch.org/whl/cu121

# Resto de dependencias
pip install -r requirements.txt

Configurar pyannote (token Hugging Face)

Aceptar los términos de uso de los modelos pyannote/speaker-diarization-3.1 y pyannote/segmentation-3.0 en huggingface.co. Luego:

# En config.yaml, agregar:
huggingface:
  token: "hf_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

Inicializar base de datos y estructura de directorios

python scripts/03_inicializar_bd.py

Este script crea los directorios data/, data/audios/, data/exportaciones/, data/fotos/, data/chroma_store/, logs/; inicializa data/ecco.db con todas las tablas y FTS5; y ejecuta el seed de 21 concejales 2024-2027.

Configurar config.yaml

Copiar config.example.yaml a config.yaml y ajustar los valores del municipio (ver §9).

Iniciar la aplicación

streamlit run app.py --server.port 8501 --server.address 0.0.0.0

Abrir en navegador: http://localhost:8501

Verificación exitosa El dashboard debe mostrar el conteo de concejales (21), el estado "Ollama ✓" en verde, y la GPU detectada con VRAM disponible.

Inicio automático con Windows Task Scheduler

# Crear tarea programada para inicio automático
schtasks /create /tn "ECCO" /tr ^
    "C:\ECCO\.venv\Scripts\streamlit.exe run C:\ECCO\app.py" ^
    /sc onlogon /ru SYSTEM /f
09

Configuración — config.yaml

Toda la configuración del sistema se centraliza en config.yaml en la raíz del proyecto. A continuación se documenta cada sección con sus campos y valores por defecto.

# ─────────────────────────────────────────────────────────────
# config.yaml — ECCO v1.0 — Configuración completa
# ─────────────────────────────────────────────────────────────

municipio:
  nombre: "Concejo de Medellín"       # Nombre que aparece en actas y UI
  ciudad: "Medellín"
  departamento: "Antioquia"
  pais: "Colombia"
  logo_path: "assets/logo_concejo.png"  # Logo para cabecera de documentos
  periodo_actual: "2024-2027"
  num_concejales: 21                   # Total de escaños
  quorum_ordinario: 11                 # Mayoría simple para quórum
  quorum_calificado: 14                # 2/3 del total para temas especiales

base_datos:
  path: "data/ecco.db"                 # Ruta al archivo SQLite
  backup_enabled: true
  backup_path: "data/backups/"
  backup_interval_days: 1             # Backup diario automático
  max_backups: 30                      # Conservar últimos 30 backups

ollama:
  base_url: "http://localhost:11434"   # URL del servicio Ollama
  llm_model: "qwen2.5:7b"              # Modelo LLM para análisis
  embed_model: "nomic-embed-text"      # Modelo para embeddings
  timeout_seconds: 120                 # Timeout por llamada LLM
  temperature: 0.1                     # Temperatura (bajo = más determinista)
  max_tokens: 4096                     # Máximo tokens de respuesta

pipeline:
  denoising_enabled: true              # Activar DeepFilterNet
  whisper_model: "large-v3"            # large-v3 | medium | small
  whisper_compute_type: "int8"         # int8 | float16 | float32
  whisper_device: "cuda"               # cuda | cpu
  whisper_language: "es"              # Código ISO de idioma
  chunk_duration_seconds: 1800         # 30 min por chunk
  chunk_overlap_seconds: 10
  diarization_enabled: true
  diarization_min_speakers: 2
  diarization_max_speakers: 40
  sentiment_enabled: true
  sentiment_batch_size: 32

busqueda:
  fts_weight: 0.40                     # Peso del componente FTS5
  vector_weight: 0.60                  # Peso del componente vectorial
  top_k: 20                            # Resultados máximos a devolver
  chunk_size: 500                      # Caracteres por chunk para embeddings
  chunk_overlap: 50
  chroma_path: "data/chroma_store"

huggingface:
  token: "hf_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"  # Token de acceso HF

audio:
  sample_rate: 48000                   # Hz — no cambiar (requerido por Whisper)
  channels: 1                          # Mono
  target_dbfs: -20                     # Normalización de volumen
  min_duration_seconds: 30             # Duración mínima de audio aceptada
  audio_path: "data/audios"

documentos:
  export_path: "data/exportaciones"
  docx_template: "assets/plantilla_acta.docx"  # Plantilla base Word
  pdf_font: "Times-Roman"              # Fuente para PDFs reportlab
  watermark_borrador: true             # Agregar marca "BORRADOR" en actas no aprobadas

ui:
  titulo: "ECCO — Concejo de Medellín"
  favicon: "assets/favicon.ico"
  items_por_pagina: 20
  tema_color: "#0D2E6E"               # Color primario de la UI

logs:
  path: "logs/"
  level: "INFO"                        # DEBUG | INFO | WARNING | ERROR
  max_bytes: 10485760                  # 10 MB por archivo de log
  backup_count: 5
10

Adaptación a Otros Municipios

ECCO está diseñado para ser replicable en cualquier concejo municipal con cambios mínimos, todos concentrados en archivos de configuración y datos. No se requiere modificar código fuente para la mayoría de adaptaciones.

10.1 Cambios en config.yaml

CampoQué ajustar
municipio.nombreNombre del concejo (aparece en UI, encabezados y actas)
municipio.num_concejalesTotal de escaños del concejo destino
municipio.quorum_ordinarioMayoría simple según reglamento interno
municipio.quorum_calificadoMayoría calificada según reglamento interno
municipio.logo_pathLogo institucional del concejo
documentos.docx_templatePlantilla Word con membrete y estilos del concejo

10.2 Reemplazar el seed de concejales

El script scripts/02_seed_concejales.py contiene los 21 concejales del periodo 2024-2027 de Medellín. Para otro municipio:

  1. Editar el array CONCEJALES en el script con los nombres, partidos y comisiones del municipio destino.
  2. Re-ejecutar: python scripts/02_seed_concejales.py --reset
  3. Cargar fotos en data/fotos/ con nombre concejal_<id>.jpg

10.3 Plantilla de acta

El prompt de generación de acta en src/intelligence/acta_generator.py incluye una sección de configuración de plantilla. Los campos editables son:

  • ENCABEZADO_LEGAL: Texto de apertura conforme al reglamento del concejo
  • FORMULA_APERTURA: Frase protocolar de apertura de sesión
  • FORMULA_CIERRE: Frase protocolar de cierre
  • SECCIONES_ACTA: Lista de secciones obligatorias según normativa local

10.4 Base legal

Importar los documentos legales del municipio destino (Estatuto de Presupuesto, Reglamento Interno, Acuerdo Marco, POT, etc.) a través de la página Base Legal en la UI. El sistema los indexa automáticamente con FTS5 y vectores.

10.5 Adaptación de idioma / variante regional

Para concejos donde el español regional difiere significativamente (ej: España, México), se puede ajustar:

  • pipeline.whisper_language: código ISO (siempre es para español)
  • Los prompts de LLM en src/intelligence/ incluyen instrucción de idioma; editar si se usa variante castellana diferente.
  • El modelo pysentimiento/robertuito está entrenado en español latinoamericano; para España, evaluar cambiar a pysentimiento/bertweet-base-sentiment-analysis.
Municipios con menos de 13 concejales Ajustar diarization_max_speakers en config al número real de concejales + 2 (para presidencia y secretaría). Esto mejora la calidad de la diarización al no buscar más hablantes de los que hay.
11

Limitaciones Conocidas y Roadmap

11.1 Limitaciones en v1.0

ÁreaLimitaciónImpactoMitigación
Diarización Confusión entre hablantes con voz similar o micrófonos lejanos; error típico 10-20% Medio Editor manual de asignación de hablante por segmento
Transcripción ASR WER ~5-8% con audio limpio; puede subir a 15-25% con ruido, acentos regionales o terminología técnica Medio Denoising activado + editor inline de corrección
Análisis LLM qwen2.5:7b puede alucinar en votaciones con resultado ambiguo Alto Campo de confianza; revisión obligatoria antes de aprobación
Concurrencia SQLite no soporta escrituras concurrentes; solo una instancia de pipeline a la vez Bajo (uso típico) Cola de procesamiento FIFO; bloqueo de UI durante pipeline activo
Autenticación Sin sistema de usuarios ni control de acceso en v1.0 Alto (seguridad) Desplegar en red interna institucional únicamente
GPU requerida Sin GPU NVIDIA CUDA, el pipeline es 5-10× más lento (CPU mode) Medio Usar modelo Whisper medium o small en instalaciones sin GPU
Idiomas Optimizado para español latinoamericano; otros idiomas o variantes tienen menor rendimiento Bajo (scope) Configurable via whisper_language
Backup Backup automático solo del archivo SQLite; ChromaDB no está incluido Medio Script de backup completo planificado para v1.1

11.2 Roadmap

v1.1 — Seguridad y producción
  • Sistema de autenticación (usuario/contraseña + roles: admin, secretario, concejal)
  • Backup completo automatizado (SQLite + ChromaDB + audios)
  • HTTPS con certificado autofirmado para acceso en red local
  • Rate limiting en llamadas a Ollama
  • Logging de auditoría de acciones
v1.2 — Calidad de transcripción
  • Diccionario de términos institucionales para Whisper (hotwords)
  • Fine-tuning de diarización con audios del concejo
  • Identificación automática de hablante por voz (voice fingerprinting)
  • Corrección ortográfica post-ASR con LanguageTool
v1.3 — Funcionalidades avanzadas
  • API REST para integración con sistemas externos (SIGAME, ORFEO)
  • Notificaciones por email/Telegram al completar pipeline
  • Módulo de comparativa entre sesiones (tendencias)
  • Exportación a XML según estándar Akoma Ntoso (documentos legislativos)
v2.0 — Escala multi-concejo
  • Arquitectura multi-tenant (varios concejos en misma instancia)
  • Migración a PostgreSQL con pgvector (remplazo SQLite + ChromaDB)
  • Despliegue Docker Compose
  • Panel de administración centralizado para PINGS
  • Modelo LLM propio fine-tuneado en actas colombianas
Nota sobre el roadmap Las fechas de entrega de cada versión se coordinan con el cliente. Las funcionalidades listadas son tentativas y pueden reordenarse según prioridades institucionales del Concejo de Medellín.