Skip to main content

government-data-pipelines

Patrones para ingerir, estructurar y visualizar datos de fuentes gubernamentales — scraping de webs institucionales, parsers de PDFs con estructura fija, normalización a base de datos, dashboards interactivos. Catálogo de 35+ APIs de datos abiertos españolas (BORME, AEMET, INE, ESIOS, etc.). Incluye casos CIAF (ferroviario), CIAIAC (aviación), CIAIM (marítimo). Patrón Agregador Multi-Fuente para combinar múltiples APIs en dashboards unificados.

Jump to install

Source facts

Repository
Ntizar/NtizarBrainMasterMind
Last source activity
June 30, 2026 at 12:46
Detected SKILL.md language
Spanish
Stars
2
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
29 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
government-data-pipelines
version
1.4.0
description
Patrones para ingerir, estructurar y visualizar datos de fuentes gubernamentales — scraping de webs institucionales, parsers de PDFs con estructura fija, normalización a base de datos, dashboards interactivos. Catálogo de 35+ APIs de datos abiertos españolas (BORME, AEMET, INE, ESIOS, etc.). Incluye casos CIAF (ferroviario), CIAIAC (aviación), CIAIM (marítimo). Patrón Agregador Multi-Fuente para combinar múltiples APIs en dashboards unificados.
tags
["government","scraping","pdf-parsing","data-ingestion","dashboard","geolocation"]
# Government Data Pipelines — Patrones de ingestión de datos gubernamentales ## Resumen Procedimiento sistémico para convertir documentos públicos gubernamentales (informes, estadísticas, registros) en bases de datos estructuradas + visualizaciones interactivas. Los documentos oficiales españoles suelen tener estructura fija y URLs predecibles. ## Casos de uso ### 1. Informes de la CIAF (ferroviario) - **Fuente:** https://www.transportes.gob.es/organos-colegiados/ciaf - **URLs PDF:** Varios patrones (ver `references/ciaf-scraping.md` — 4 patrones según año) - **Estructura fija:** Resumen → Descripción → Análisis → Conclusiones → Recomendaciones - **Schema:** Ver `templates/ciaf-report-schema.json` en skill `liteparse-document-ai-parsing` - **Scraping:** Ver `references/ciaf-scraping.md` (dentro de este skill) - **⚠️ Disponibilidad:** 2007-2025 están publicados. **277 informes** (ya descargados en `/root/workspace/CIAF/`) + 17 memorias + 7 normativas = 301 PDFs total. **⚠️ PATRÓN DE URL POR AÑOS (CRÍTICO — verificado 2026-06-26):** - **2007-2016:** `/MFOM/LANG_CASTELLANO/ORGANOS_COLEGIADOS/CIAF/INFORMES/YYYY/` - Ej: `/MFOM/LANG_CASTELLANO/ORGANOS_COLEGIADOS/CIAF/INFORMES/2009/` - Los PDFs están en el HTML estático (no AJAX) - Patrón de PDF: `href="(/recursos_mfom/pdf/UUID/ID/FILENAME.pdf)"` (comillas dobles) - Total: ~181 PDFs (2009-2016; 2007-2008 vacíos) - **2017-2025:** `/organos-colegiados/ciaf/informes-finales-de-sucesos-investigados/infofin-YYYY` - Ej: `/infofin-2025` - PDFs en `/recursos_mfom/paginabasica/recursos/XXXX-YY-ZZZZ-if-*.pdf` - Total: ~38 PDFs (ya descargados) - **NO usar filtros GET** (`?field_ciaf_anyo_value=2015`) — no funcionan, solo devuelven menú. - **TOTAL REAL: 277 informes** (ya descargados en `/root/workspace/CIAF/YYYY/` desde 2007 hasta 2025). Distribución: 2007:4, 2008:53, 2009:43, 2010:28, 2011:24, 2012:22, 2013:23, 2014:14, 2015:10, 2016:11, 2017:12, 2018:2, 2019:3, 2020:3, 2021:6, 2022:5, 2023:3, 2024:3, 2025:1. **⚠️ TRES ERAS DE FORMATO (CRÍTICO para el parser):** Los informes tienen 3 formatos distintos según la normativa vigente: 1. **Pre-RD 810/2007 (2007-2008):** Formato libre, secciones variables (Antecedentes/Hechos/Análisis) 2. **RD 810/2007 (2009-2013):** Secciones 1-5 (Resumen, Descripción, Análisis, Conclusiones, Recomendaciones) 3. **RD 623/2014 (2014-2025):** Secciones 0-6 (Abreviaturas, Resumen, Descripción, Análisis, Conclusiones, Recomendaciones, Anexos) - **Normativa de referencia:** `/root/workspace/CIAF/normativa/04-RD_623_2014_ciaf.pdf` — Art. 15 define la estructura obligatoria del informe - **Detectar era por año** antes de parsear. Los 2007-2008 son los más irregulares. **📐 Calidad profesional — requisito mínimo para herramientas de equipo (VERIFICADO 2026-06-26):** Cuando el usuario dice "tengo que enviárselo al equipo y crearles una herramienta que puedan usar ellos", el estándar es **calidad de producción, no prototype**. Checklist mínimo: - **100% de informes con título descriptivo** (no "Informe NN/YYYY") - **0% HTML como texto plano** en el frontend ( `<strong>` se renderiza como bold, no como literal) - **0% etiquetas duplicadas** en el panel de detalle - **Nombres de campos consistentes** entre parser→index→frontend (mismo idioma, mismo nombre) - **Estaciones limpias** (sin frases del PDF como nombre) - **Fechas completas** (sin campos vacíos en informes con PDF fuente) - **Enlaces funcionales** (PDF local + enlace oficial CIAF) Si algún dato es visible para el usuario final, debe ser correcto y presentable. No vale "más o menos" — el usuario lo envía a su equipo y se juega su credibility. **📊 Memorias anuales:** - URL: `/organos-colegiados/ciaf/memorias-anuales/memoriasanuales` - 17 memorias (2008-2024), guardadas en `/root/workspace/CIAF/memorias/` - PDFs en el HTML directamente (no AJAX) — buscar `href='(/recursos_mfom/[^']+\\.pdf)'` - Pattern de PDFs: múltiples rutas (`/recursos_mfom/`, `/recursos_mfom/pdf/UUID/`, `/recursos_mfom/listado/recursos/`) - **NO hay memoria de 2011** — sí existe, pero se saltó en script anterior. Verificar siempre. - **📄 Normativa:** 7 PDFs en `/root/workspace/CIAF/normativa/` — incluye RD 623/2014 que define estructura de informes **📐 Preferencia de arquitectura (verificado):** - **JSON como fuente de verdad** (no MD, no SQLite): GitHub Pages sirve JSON estático - **JSON particionado por año**: `data/reports/YYYY.json` + `data/index.json` ligero (~50KB para 500 registros) - **relations.json** para cruzar entidades × informes × recomendaciones - **Imágenes extraídas** de PDFs con `pdfimages` (poppler) + `pdftoppm` como fallback - **Coherencia** entre memorias anuales e informes individuales — verificar que los datos coinciden - **Auto-import**: pipeline `sync.py` que detecta nuevos PDFs en la web y los procesa automáticamente ### 2. Informes de la CIAIAC (aviación) - **Fuente:** https://www.transportes.gob.es/organos-colegiados/ciaiac - **Mismo patrón** que CIAF pero para accidentes aéreos ### 3. Informes de la CIAIM (marítimo) - **Fuente:** https://www.transportes.gob.es/organos-colegiados/ciaim - **Investigaciones organizadas por año:** `/organos-colegiados/ciaim/investigaciones/2024` ### Arquitectura para datasets grandes (>100 registros, GitHub Pages) Cuando hay 200+ registros, NO usar un solo JSON gigante. Usar **JSON particionado por año**: ``` data/ ├── index.json ← Índice ligero (todos los IDs, metadatos mínimos, ~50KB) ├── reports/ │ ├── 2009.json ← Registros del 2009 │ ├── 2010.json ← Registros del 2010 │ └── ... ├── relations.json ← Entidades × registros × relaciones └── images/ ├── 2009/ │ └── IF-001-2009-fig01.png └── ... ``` **Por qué funciona:** - `index.json` se carga una vez para mapa + filtros (50KB para 500 registros) - `reports/YYYY.json` se carga solo al hacer clic en un registro de ese año - GitHub Pages sirve JSON estático sin backend - Escalable a 500+ registros sin problemas de rendimiento **Por qué NO SQLite en navegador:** - GitHub Pages no sirve SQLite — necesitarías sql.js (500KB) + WebAssembly - Para <1000 registros, JSON con fetch es suficiente - JSON es más fácil de mantener y versionar con git ### Pipeline genérico (paso a paso) ### Paso 1: Descubrimiento de fuentes ```bash # Acceder a la web con curl (NO browser tool — 403 en transportes.gob.es) curl -sL 'https://www.transportes.gob.es/organos-colegiados/ciaf/informes-finales-de-sucesos-investigados' \ -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36' # Extraer enlaces a PDFs curl ... | grep -oP 'href="[^"]*\.pdf"' | sort -u ``` **⚠️ 4 patrones de URL para PDFs en transportes.gob.es:** 1. `recursos_mfom/paginabasica/recursos/XXXX-YY-ZZZZ-if-*.pdf` (2017-2025) 2. `recursos_mfom/pdf/UUID-UUID/UUID/FILENAME.pdf` (2015-2016) 3. `recursos_mfom/YYMMDD-YYMMYY-if-*.pdf` (2015-2016) 4. `recursos_mfom/comodin/recursos/YYMMDDYYMMYYif*.pdf` (2016) **⚠️ 2 patrones de URL por año:** - 2015-2016: `/informes-finales-de-sucesos-investigados/AÑO` - 2017-2025: `/informes-finales-de-sucesos-investigados/infofin-AÑO` - **NO usar filtros GET** (`?field_ciaf_anyo_value=2015`) — no funcionan. ### Paso 2: Scraping masivo - **Frecuencia:** Cron semanal (domingo 06:00 UTC) - **Detección de nuevos:** Comparar hashes de URLs con base de datos local - **Velocidad:** 1 PDF/sec es suficiente para cientos de informes ### Paso 3: Extracción de texto - **Herramienta principal:** `PyMuPDF` (`fitz`) — más fiable que poppler/markitdown, puro Python, sin dependencias del sistema ```python import fitz doc = fitz.open(str(pdf_path)) text = "\n".join(page.get_text() for page in doc) doc.close() ``` - **Fallback:** `markitdown` con extras: `pip install "markitdown[pdf]"` (requiere `pymupdf` como backend de PDF) - **⚠️ `pdftotext` (poppler) frágil en entornos containerizados:** puede fallar por `libpoppler.so.XXX` faltante. Si aparece `No such file or directory: 'pdftotext'`, instalar `libpoppler-dev` O cambiar a PyMuPDF. - **Extracción de imágenes:** `PyMuPDF` también extrae imágenes incrustadas (`page.get_images()` + `doc.extract_image(xref)`). Si no hay imágenes incrustadas, renderizar páginas como PNG con `page.get_pixmap(dpi=150)`. - **OCR:** Si el PDF es imagen (no texto), usar `ocrmypdf` ### Paso 3b: Extracción por páginas vs regex completo (VERIFICADO 2026-06-26) **⚠️ PATRÓN CRÍTICO: Extraer por páginas, NO por regex sobre texto completo.** El enfoque regex sobre texto completo falla porque: - El TOC (índice con puntos `.....39`) se confunde con contenido real - Headers/footers de cada página se mezclan con el contenido - Secciones de diferentes idiomas (inglés al final) contaminan la extracción **Enfoque correcto — extracción por páginas:** ```python def extract_pages(pdf_path: str) -> list[str]: """Extrae texto de cada página por separado.""" doc = fitz.open(pdf_path) pages = [page.get_text() for page in doc] doc.close() return pages def find_section_pages(pages: list[str]) -> dict[int, int]: """Encuentra en qué página empieza cada sección (saltando TOC).""" section_pages = {} for i, text in enumerate(pages): if i < 2: # Skip cover + warning continue if re.search(r'\.{10,}', text): # Skip TOC pages continue for m in re.finditer(r'(?:^|\n)\s*(\d+)\.\s+([A-ZÁÉÍÓÚÑ][A-ZÁÉÍÓÚÑ\s]{5,40})', text): num = int(m.group(1)) if num not in section_pages: section_pages[num] = i return section_pages def get_pages_text(pages, start_page, end_page): """Concatena páginas limpiando headers/footers individuales.""" text = "" for i in range(start_page, min(end_page, len(pages))): page_text = pages[i] # Limpiar headers/footers de CADA página page_text = re.sub(r'Comisión de Investigación de\s*Accidentes Ferroviarios', '', page_text) page_text = re.sub(r'Informe Final de la CIAF\s+\d+/\d{4}', '', page_text) page_text = re.sub(r'^\s*\d{1,2}\s*$', '', page_text, flags=re.MULTILINE) # page numbers page_text = re.sub(r'^.*\.{10,}.*$', '', page_text, flags=re.MULTILINE) # TOC remnants text += page_text + "\n" return text.strip() ``` **Ventajas comprobadas:** - 2024: 3/3 títulos, 3/3 conclusiones, 3/3 recomendaciones (vs 0/3 con regex completo) - 2009: 43/43 títulos, 38/43 conclusiones, 25/43 recomendaciones - Maneja 3 eras de formato automáticamente **Detección de TOC:** Líneas con 10+ puntos consecutivos (`.{10,}`) → saltar página completa **Manejo de bilingüismo:** Los informes 2014+ tienen sección en inglés al final. Cortar extracción de recomendaciones antes de "SAFETY RECOMMENDATIONS" o "English summary": ```python for i in range(start + 1, min(start + 5, len(pages))): if re.search(r'SAFETY\s+RECOMMENDATIONS|English\s+summary', pages[i], re.IGNORECASE): end = i break ``` **Script completo:** `/root/workspace/CIAF-visor/scripts/parse_year_v2.py` — parser funcional con extracción por páginas, geocoding local, y manejo de 3 eras. Ver también: `references/page-based-pdf-extraction.md`, `references/station-coords-geocoding.md`, y `references/ciaf-memoria-parsing.md` (parseo de memorias anuales, diferente de informes individuales). ### Paso 3c: Extracción semántica de campos El mayor reto NO es extraer texto del PDF (PyMuPDF lo hace bien), sino **estructurar el texto libre en campos JSON**. **⚠️ PARADIGMA SHIFT (2026-06):** Para PDFs digitales con texto seleccionable, el enfoque LLM (`pdf-llm-extraction`) supera cualitativamente a los regex: - **LLM:** 270/270 conclusiones (100%), 268/270 recomendaciones (99%), 381 trenes - **Regex:** 194/270 conclusiones (72%), 149/270 recomendaciones (55%), 0 trenes **Usar `pdf-llm-extraction` para batch processing de PDFs digitales.** Los regex de abajo quedan como referencia para entender la estructura de los informes, pero no son la herramienta principal recomendada. Patrones verificados con CIAF (regex, referencia histórica): **Expediente/informe — 4 patrones de título según era (VERIFICADO 2026-06-26):** Los informes CIAF tienen 4 formatos de título distintos. Detectar por orden de prioridad: ```python # Patrón 1 (2007-2008): "Investigación del accidente ferroviario ocurrido en..." m = re.search(r'Investigaci[oó]n del accidente.*?ocurrido.*?(\d{1,2})[/.-](\d{1,2})[/.-](\d{4})', text[:2000]) # Patrón 2 (2015-2018): "CIAF Nº X/XXXX" o "IF X/XXXX" m = re.search(r'(?:CIAF|Informe)\s+(?:N[º°]|n[º°])\s*(\d+/\d{4})', text[:2000]) # Patrón 3 (2019-2021): "expediente nº X/XXXX ocurrido el DD.MM.YYYY"
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub