Estrategia de retrieval diario — marvelousdb (Fly)
Contexto: marvelous ( superhero DB en Fly, app marvelousdb ) tiene errores diarios
en el scraping de datos faltantes. Diagnóstico y plan del 2026-09-06.
DIAGNÓSTICO REAL (verificado 2026-09-06 con cron.log + sqlite en servidor)
18 “paso fallo” acumulados. La corrida de hoy (09:00 UTC, no el wake de 14:00 — el loop sleep-86400 deriva a través de stop/start, confirmado):
| Paso | Resultado | Estado real |
|---|---|---|
| Browser Run imágenes | 0 cached, 0 sin imagen, 968 en índice | AGOTADO |
| Comic Vine DC | offset 436,600 / ~436,700, 0 insertados, 44,270 DC en BD | AGOTADO (escaneo total hecho) |
| DeepL | 18 traducidos, quedan 39,825 (422/40k) | ÚNICO backlog real — pero 39,825 × ~800 chars ≈ 32M chars; free tier 500k/mes → ~20/día ≈ 5 años. Inconvergente por diseño. |
| Wikipedia | 23/23 categorías “ya hecho”, 0 personajes | AGOTADO |
| SerpAPI imágenes | 0 sin imagen | AGOTADO |
Conclusión: NO son errores de red ni de cupo pisado. Son 4 de 5 fuentes sin backlog que corren igual cada día y fallan al detectar “nada que hacer” + el paso que sí produce (DeepL) no puede converger con el cupo gratuito.
ESTRATEGIA CORREGIDA (reemplaza el plan original de esta sección hacia abajo)
1. Estados de fuente en el ledger (cron-state.json)
Cada paso: active | exhausted | failing. Reglas de transición:
- Carrera completa con 0 items elegibles →
exhausted(exit 0, se OMITE los días siguientes). - Sweep periódico (semanal/mensual) reactiva temporalmente para detectar additions (personajes nuevos en Comic Vine, imágenes recién reportadas desde el sitio).
- Efecto inmediato: el log diario pasa de 5 pasos ruidosos a 1, errores a cero.
2. Retirar el bulk-scan de Comic Vine
El offset ya recorrió los ~436k. No borrar el progress, pero el paso se marca exhausted. El retrieval futuro de DC es quirúrgico: search-by-name por personaje reportado desde el sitio (misma API, 1 request por caso), no escaneo masivo.
3. DeepL: resolver el backlog con Ollama local (local-first)
El servidor deja de traducir. Script nuevo data/translate-ollama.js corre en el
System76 (modelo tipo gemma/nllm para ES→EN→DE de intros de lore), produce el mismo
formato de lore-translations.json, se sube al volumen vía fly ssh sftp put o
endpoint interno. 32M chars en local = costo cero, sin cupo, convergible.
El worker del servidor queda SOLO para sweeps de fuentes freshly-agotadas.
4. Scheduling e higiene (vigente del plan original)
- Quitar
while true; sleep 86400del entrypoint; correr una vez por boot con gate de fecha UTC en el ledger. - Exit codes: 0 ok · 3 sin-cupo (esperado) · 1 error real.
- Eliminar ruido
--env-file-if-exists=.enven el contenedor (allá todo es secrets).
Orden de implementación
Ledger + estados + gateHECHO 2026-09-07:data/cron-imports.jsreescrito (STEPS conempty(out), gate por fecha UTC, sweep a los 7d,--force/--sweep/--dry-run) yentrypoint.shsin loop. Probado: 13/13 casos de regex contra cron.log real + escenarios B–E de gate/sweep/fallo.- translate-ollama.js local + sftp push → prompt listo en
~/nef/decisions/[CREDENCIAL].md. - needs_work/backoff por personaje (tabla sqlite) → solo si el sitio empieza a reportar casos imposibles; no construir antes de tiempo.
Falta: fly deploy para activar (o esperar el siguiente merge + deploy normal).
Addendum 2026-09-07 (tarde) — higiene de stubs + retrieval de un comando
IMPLEMENTADO y desplegado-en-volumen:
data/lib/populated.js: regla única → personaje “visible” si bio ≥ 40 chars O imagen real (image_not_availableyblank.pngde Comic Vine NO cuentan).- Columna
characters.populated: la calculan build-sqlite, comicvine y wikipedia al insertar;data/refresh-populated.js= migración idempotente + recomputo (incluye imágenes del cache local) como paso [0] del batch diario. api.js:/api/characters(browse/search/random), picker de nombres y random de/api/battlefiltranpopulated = 1;blank.pngrinde no-image.svg. Los stubs siguen accesibles por id directo y visibles en coverage — no se borran nada.- Comic Vine ya no inserta stubs (skip si ni bio ni imagen real).
- Backfill ejecutado en servidor: 54,201 visibles / 1,714 ocultos. Jack Irons ([ID-TEL]) confirmado populated=0.
- Un comando:
npm run retrieve(batch local),retrieve:sweep,retrieve:server(POST /api/admin/retrieve con Bearer RETRIEVE_TOKEN — corre dentro de la máquina con sus secrets y el ledger; ignora gate), yretrieve:status(ledger + tail del cron.log por HTTP). Pendiente:fly secrets set RETRIEVE_TOKEN=<openssl rand -hex 16>y reflejarlo en .env.
CRÍTICO: sin fly deploy, el contenedor viejo (loop sleep-86400 + código
anterior) volverá a correr ruidoso ~al despertar de mañana 14:00 UTC. El
deploy debe ocurrir antes, o la máquina quedará una última jornada ruidosa.
Incidente post-deploy 2026-09-06 ~17:30 (resuelto)
El boot del código nuevo reveló un bug LATENTE: CACHE_DIR de cache-images.js
resolvía por __dirname/../public/img/cache (no migrado al volumen en
df3dd85f) y la imagen hornea ese directorio REAL con archivos viejos → el
guard if [ ! -e ] del entrypoint nunca enlazaba el symlink → los archivos
cacheados escribían en storage efímero (perdidos en cada deploy) y el prune de
boot barrió 909/968 entradas del índice al no ver sus archivos.
- Reparación en caliente:
repair-cache.js(rescató 121 archivos del dir público, reconstruyó el índice = 1,369 entradas desde los 1,380 archivos del volumen) + symlink forzado. Ningún archivo se perdió. - Código:
CACHE_DIR = dirname(DB_PATH)/img-cache+ entrypoint reemplaza directorios stale por el symlink ([ ! -L ]) + web.js now forwards the Authorization header (el trigger externo devolvía 401 tras el proxy). - Commits de la jornada: d785831c, 4d75231d (ledger/gate), fc870b77 (higiene + trigger), 111b8e1b (fix volumen/proxy). Desplegado y verificado: batch de arranque silencioso (18 DeepL, todo lo demás EXHAUSTED), imágenes 200 desde el volumen, stubs fuera de búsqueda.
Addendum 2026-09-07 — fuentes para el gap “personajes vacíos”
Síntoma: /character/[ID-TEL] (Jack Irons) vacío. Investigado en vivo:
- DC sin bio: 7,469 / 44,270. El bulk-search de Comic Vine guarda documentos
finos; probé el endpoint de detalle (
/character/4005-XXXXXX/?field=site_detail) con Jack Irons + 8 muestras: site_detail = 0 chars en todos. Son stubs de Comic Vine (0 aparcciones reales; el filtro min-appearances no los atrapó). - Conclusión: para la mayoría el dato NO existe en Comic Vine — buscar más en la misma fuente es tirar cupo.
Fuentes evaluadas (verificadas 2026-09-07)
| Fuente | Estado | Veredicto |
|---|---|---|
dc.fandom.com API MediaWiki (/api.php, sin llave) | PROBADA: search responde, miles de páginas de personajes DC con bio real, incluso menores | Mejor opción para las 7.5k sin bio; reusar el patrón fandomPass de cache-images.js, ahora para lore |
Comic Vine bulk publisher:Marvel Comics | Mismo pipeline probado del scan DC | Corrige desbalance: 774 Marvel vs 44,270 DC en BD. Budget de una semana, roster nuevo sin fuentes nuevas |
Wikipedia Category:DC Comics characters | 180 páginas directas + 32 subcategorías | Solo famosos; pipeline import-wikipedia ya sirve para esto. Marginal |
| League of Comic Geeks | sin API oficial | Descartado (scraping frágil/ToS) |
| Grand Comics Database | tiene apariciones, no bios | No ataca este gap |
Prioridad recomendada
- Higiene primero (costo 0): no indexar en FTS ni ofrecer en VS-random a
personajes sin bio Y sin imagen real (descartar URLs
blank.pngde CV como imagen). Eso elimina el síntoma de página vacía ya. - fandomPass para DC lore (fuente nueva, gratis, ilimitada-ish).
- Scan Marvel en Comic Vine si se quiere roster Marvel grande.
Nota operativa: fly ssh console NO hereda secrets; para pruebas en vivo, root +
env del proceso de /proc. fly sftp put <local> <remoto> (orden inverso a get)
y no sobreescribe: rm remoto primero.
Arquitectura actual (relevante)
entrypoint.sh: al boot →sleep 60→cron-imports.js→ loopsleep 86400..github/workflows/wake.yaml(14:00 UTC) /sleep.yaml(05:00 UTC): la máquina vives ~15h diarias; el wake es de facto el disparador del batch.data/cron-imports.js: corre 5 pasos secuenciales con presupuesto fijo de 90% del cupo por corrida (Browser Run, Comic Vine, DeepL, Wikipedia, SerpAPI).- Importadores ya son resumables:
comicvine-progress.json,wikipedia-progress.json,lore-translations.json,img-cache.json+missing-images.json.
Causa raíz de los “errores diarios”
- Redeploy/restart intra-día → el batch corre 2+ veces el mismo día → la segunda corrida se come contra 429s reales (cupo ya gastado).
- Los importadores salen con exit 1 cuando agotan cupo normalmente
(
import-comicvine.js:123,cache-images.js:390“commons 429”, DeepL 429). Un “terminé por hoy” se registra como fallo. - El
sleep 86400dentro del contenedor deriva con cada wake y muere con el stop — nunca dispara una segunda vez; solo genera falsas expectativas de scheduling.
Estrategia (orden de implementación)
1. Ledger de cupo por día UTC — data/cron-state.json
{ "runs": { "2026-09-06": { "comicvine": 180, "deeplChars": 14400, "browserRunSec": 540, "serpapi": 7, "completed": true } } }cron-imports.jslee el día UTC, calcularestante = presupuesto_diario - usado, y pasa--limit restantea cada paso. Si restante ⇐ 0, paso se omite (log claro).- Cada importador reporta cuánto consumió (o el wrapper lo estima por páginas/límites).
- Efecto: N corridas el mismo día siguen gastando 90% total, nunca más.
2. Convenio de exit codes
- 0 = completado · 3 = “sin cupo / continúa mañana” (esperado) · 1 = error real.
- En
cron-imports.js, exit 3 → línea informativa, no! paso fallo.
3. Scheduler = wake diario, no sleep interno
- Borrar el
while true; sleep 86400del entrypoint. - El batch corre en boot, una sola vez por día UTC (gate por
cron-state.json:runs[fecha].completed). - El wake de las 14:00 UTC es el cron. Los redeploys ya no causan daño (gate + ledger).
4. Cola de reintentos por personaje (convergencia)
- Tabla sqlite
needs_work(character_id, field, source, attempts, last_error, next_retry_at). - Poblada por el coverage check actual (los
missing*queries). - El batch consume solo filas
next_retry_at <= now(), ordenadas por attempts asc. - Falla →
attempts++,next_retry_at = now + backoff (1d, 3d, 7d); attempts > 4 →status='hopeless'y aparece en el dashboard de coverage para atención manual. - Efecto: casos imposibles dejan de quemar cupo diario; el retrieval converge.
Qué tocar
entrypoint.sh— quitar loop, correr una vez con gate.data/cron-imports.js— ledger + exit-code mapping + passing restantes.data/import-comicvine.js,import-deepl.js,cache-images.js,import-wikipedia.js— respetar--limitrestante y exit 3 al agotar cupo.data/build-sqlite.js/ nuevo migration — tablaneeds_work.
Verificación
node data/cron-imports.js --dry-rundos veces el mismo día: la segunda debe mostrar “restante = 0” y no spawning.- Simular restart:
fly restartintra-día → log sin 429s nuevos. - Regresar
tail -n 50 /app/data/cron.logvíafly ssh consolepara confirmar la causa raíz antes de implementar (requierefly auth loginlocal, que no estaba disponible en la sesión del diagnóstico).