Omnigraph
그래프 엔진으로 컨텍스트를 조립하고, 객체 저장소 기반에서 에이전트가 코드처럼 그래프 데이터를 분기·타입·검토·병합·감사·거버넌스합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Sistema MLOps para el reto Pulso TransMi: collector, modelos, inferencia y monitoreo
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Sistema que pronostica la demanda de pasajeros de 12 estaciones de TransMilenio a +15, +30, +45 y +60 minutos y opera ese pronóstico en el tiempo: recolecta datos nuevos, entrena y versiona modelos, predice y envía cada ciclo, mide su propio error y decide cuándo reentrenar. Proyecto del curso MLOps (Universidad Externado de Colombia). Contrato de la API: uexternadojz/pulso-transmi · API:README 전문 보기 ↗
Sistema que pronostica la demanda de pasajeros de 12 estaciones de TransMilenio a +15, +30, +45 y +60 minutos y opera ese pronóstico en el tiempo: recolecta datos nuevos, entrena y versiona modelos, predice y envía cada ciclo, mide su propio error y decide cuándo reentrenar.
Proyecto del curso MLOps (Universidad Externado de Colombia). Contrato de la API: uexternadojz/pulso-transmi · API: https://pulso-transmi.72-60-245-2.sslip.io/docs
flowchart LR
API[API Pulso TransMi<br/>stream · ciclo · submissions] -->|observaciones| COL[collector<br/>cada 30 min]
COL -->|upsert + cursor<br/>en 1 transacción| DB[(Supabase<br/>PostgreSQL + Storage)]
DB --> TR[train<br/>validación temporal]
TR -->|artefacto + ficha| REG[registro de modelos<br/>candidate → champion]
REG --> INF[predict<br/>48 targets por ciclo]
API -->|ciclo abierto, targets| INF
INF -->|entrega con Idempotency-Key| API
INF -->|predicciones + recibo| DB
DB --> MON[monitor<br/>accuracy · drift · operación]
MON -->|decisión + evidencia| DB
MON -.retrain.-> TR
| Capa | Dónde vive | Qué garantiza |
|---|---|---|
| Datos | Supabase (observations, context, stations) | Llave (station_id, observed_at): repetir una carga no duplica |
| Collector | src/pulso/ingest.py + función SQL ingest_observations | El cursor solo avanza si el lote se confirmó (misma transacción) |
| Modelos | model_versions + bucket privado models | Versión, corte de datos, commit, features, validación y hash; un solo champion |
| Inferencia | src/pulso/predict.py | Solo datos ≤ data_cutoff; exactamente los targets del ciclo; evidencia antes de enviar |
| Monitoreo | src/pulso/monitor_job.py | Accuracy móvil y acumulada, drift, salud operacional y decisión registrada |
| Automatización | GitHub Actions (.github/workflows/) | Repetible y observable; el estado vive en la base, no en el runner |
Validación temporal (3 semanas consecutivas, orígenes cada hora, métrica oficial
100 × max(0, 1 − WAPE) por estación y promediada):
| Modelo | Accuracy medio |
|---|---|
gbm_residual (champion) | 87,59 % |
ridge_residual (perfil + corrección lineal) | 87,27 % |
profile_only (perfil estacional puro) | 86,78 % |
| mismo instante de la semana pasada | 83,25 % |
| repetir el último valor | 74,42 % |
La demanda es casi puro calendario: el perfil «estación × hora de la semana» explica el 96 % de la varianza y ya roza el techo estimado (~88 %). El modelo aporta +0,8 pts y, sobre todo, robustez ante cambios: en pruebas de estrés con drift sintético amortigua cambios de nivel y tendencia, y reentrenar con solo 2 días de datos nuevos recupera la mayor parte de un cambio de patrón.
Detalle y límites honestos: reports/eda.md ·
reports/backtest.md · reports/drift_stress.md
context (clima/eventos): la API solo lo publica en el corte estático; el modelo no
debe depender de algo que no tendrá al predecir.promote_model en SQL permite rollback.src/pulso/policy.py): persistencia (no un solo periodo difícil),
datos nuevos suficientes, enfriamiento, y primero se corrige la operación antes de culpar al
modelo. Los umbrales se calibraron con el estrés de drift (y una falsa alarma real llevó a umbrales
de nivel propios de cada estación).cycle_id, versión
del modelo y las 48 predicciones, no de GITHUB_RUN_ID. Así, dos ejecuciones que entregan lo
mismo reciben el mismo recibo (HTTP 200) en vez de gastar dos de los tres intentos del ciclo.timeout-minutes: 8) despertando cada 10 min. Medimos que
GitHub descarta la mayoría de las ejecuciones programadas: con cron horario corrieron 6 de
~31, y hubo un hueco de 5 h 16 min sin ningún disparo (19:03 → 00:19 UTC del 22-09), con
Actions reportado como operational. Como la ventana dura 25 min, eso costaba la mayoría de los
ciclos. La solución: el cron sigue en */10 y admite ejecución manual, pero el job espera
dentro de la ventana y cubre 4 horas, entregando cada ciclo que abra. concurrency mantiene
uno vivo y otro en cola para relevarlo. Evidencia de que funciona: el ciclo de las 00:34 lo
capturó una ejecución programada que arrancó a las 00:19 y esperó 16 sondeos; y la cobertura
rolling 24 h pasó de 0,17 a 0,79 en doce horas.pipeline_runs/.src/pulso/ api · supa · ingest · features · model · baselines · backtest · registry
train · predict · monitor · monitor_job · policy · stress · quality
supabase/migrations/ esquema SQL reproducible (0001-0003)
scripts/ download_data.py · eda.py · run_backtests.py · drift_stress.py · practice_submit.py
reports/ análisis exploratorio, backtesting y estrés de drift (con figuras)
tests/ 165 pruebas (sin red: API y Supabase simulados)
.github/workflows/ ci · collect-and-predict · collect-and-monitor · train
docs/runbook.md puesta en marcha, operación y diagnóstico
Ver docs/runbook.md. En resumen: variables en .env (plantilla en
.env.example), Secrets del repositorio (PULSO_API_KEY, SUPABASE_URL,
SUPABASE_SERVICE_ROLE_KEY), ejecutar una vez el workflow «Entrenar candidato» para crear el
champion inicial y definir la variable PIPELINE_ENABLED=true para activar los horarios.
python -m venv .venv && source .venv/bin/activate
python -m pip install -r requirements.txt && python -m pip install -e '.[eda,dev]'
pytest # 165 pruebas, sin red
python scripts/download_data.py # baja el corte inicial a data/ (hash verificado)
python scripts/eda.py # regenera reports/eda.md
python scripts/run_backtests.py # regenera reports/backtest.md (varios minutos)
python scripts/drift_stress.py # regenera reports/drift_stress.md
python scripts/practice_submit.py # simulacro de entrega; --send para enviar
Los datos iniciales (data/*.csv) no se versionan: se descargan de la API
(/v1/downloads/*) y la fuente de verdad es Supabase.
context no se usa; si la API lo libera durante la competencia sería la mejora principal.src/pulso/monitor.py,
reports/drift_stress.md). MLflow está como EXPORTACIÓN, no como registro primario: ver abajo.next_cursor) con datos reales no se pudo comprobar
(el reloj sigue en waiting); el collector relee la última página y tolera un cursor rechazado.El registro de verdad del pipeline es la tabla model_versions en Supabase: ahí se decide la
promoción, de ahí lee el predictor y ahí está la trazabilidad, con verificación de sha256 del
artefacto, linaje por parent_version y un índice único que garantiza un solo champion a la vez.
scripts/mlflow_export.py exporta ese registro a MLflow para tener su interfaz sobre datos
reales del proyecto. No instrumenta el entrenamiento y no inventa ninguna corrida: lee las
versiones ya registradas y las escribe con sus params, métricas y linaje. Conviene decirlo así
cuando se presente, porque es lo que es.
Se eligió exportar en vez de instrumentar por una razón operativa: el pipeline corre en ejecutores efímeros de GitHub Actions, y poner MLflow como registro primario pedía exponer la cadena de conexión de Postgres a Actions y duplicar la fuente de verdad, con el riesgo de que las dos se desincronicen.
MLflow vive en su propio entorno porque requirements.txt fija versiones exactas (un artefacto de
scikit-learn solo carga con la versión con que se entrenó) y MLflow arrastra las suyas:
# Entorno aparte, una sola vez. cryptography 43 porque las versiones nuevas no traen wheel
# para macOS x86_64 y caerían a compilar con Rust.
python3.12 -m venv .venv-mlflow
.venv-mlflow/bin/python -m pip install "cryptography==43.0.3" mlflow
# Exportar e inspeccionar (SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY en el entorno)
.venv-mlflow/bin/python scripts/mlflow_export.py
.venv-mlflow/bin/mlflow ui --backend-store-uri sqlite:///mlflow.db --port 5055
# y abrir http://127.0.0.1:5055
El puerto va explícito a propósito: en macOS el 5000 por defecto de MLflow lo ocupa el receptor de AirPlay del Centro de Control, que responde 403, así que la interfaz se ve vacía sin que el error sea evidente. Si hace falta liberar el 5000, se apaga en Ajustes del sistema, General, AirDrop y Handoff, Receptor de AirPlay.
El respaldo es sqlite y no archivos porque el Model Registry de MLflow no funciona sobre el almacén de archivos. Volver a correr el script es idempotente: omite las corridas ya exportadas.
Lo que queda en la interfaz: experimento pulso-transmi con una corrida por versión entrenada
(321 al cierre de la ronda: 1 champion, 243 archivadas, 77 rechazadas), cada una con sus params,
sus métricas de validación incluidas las de por horizonte y por estación, y etiquetas con el
commit, la decisión de la puerta y su motivo; más el modelo pulso-transmi-demanda registrado,
con alias champion y el artefacto del champion adjunto.
MIT
ingestion_runsanon/authenticated; solo la
service role (Secrets de Actions) escribe. Ninguna clave se imprime ni se guarda en la base.