{"id":51036392,"url":"https://github.com/hernangm/mia204_01","last_synced_at":"2026-06-22T06:40:56.461Z","repository":{"id":350092745,"uuid":"1181732352","full_name":"hernangm/mia204_01","owner":"hernangm","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-23T18:59:35.000Z","size":413,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-23T20:25:30.850Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hernangm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-14T14:51:49.000Z","updated_at":"2026-05-23T18:58:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hernangm/mia204_01","commit_stats":null,"previous_names":["hernangm/mia204_01"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hernangm/mia204_01","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hernangm%2Fmia204_01","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hernangm%2Fmia204_01/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hernangm%2Fmia204_01/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hernangm%2Fmia204_01/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hernangm","download_url":"https://codeload.github.com/hernangm/mia204_01/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hernangm%2Fmia204_01/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34637937,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-22T06:40:55.553Z","updated_at":"2026-06-22T06:40:56.451Z","avatar_url":"https://github.com/hernangm.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Trabajo Integrador — IA en Producción\n## Pronóstico de Producción de Hidrocarburos\n\nPlataforma de ML Engineering para predecir la producción mensual de gas de pozos no convencionales, expuesta como API REST y orquestada con Apache Airflow.\n\n\u003e **Foco:** ML Engineering (reproducibilidad, trazabilidad, automatización), no precisión del modelo.\n\n---\n\n## Arquitectura\n\n```\n┌────────────────────────────────────────────────────────────────────┐\n│                         docker-compose up                          │\n├──────────┬──────────┬──────────┬───────────┬──────────┬───────────┤\n│ postgres │  redis   │ airflow  │  mlflow   │   api    │ ray-serve │\n│  :5433   │          │  :8080   │  :9090    │  :8000   │  :8265    │\n└──────────┴──────────┴────┬─────┴───────────┴────┬─────┴───────────┘\n                           │                       │\n              ┌────────────┴──────────┐   ┌────────┴────────────┐\n              │   DAG ml_pipeline     │   │   GET /forecast     │\n              │   (1° de cada mes)    │   │   GET /wells        │\n              │                       │   │   GET /health       │\n              │  descarga CSV         │   └─────────────────────┘\n              │  → preprocesa         │\n              │  → Feature Store      │   ┌─────────────────────┐\n              │  → entrena 4 modelos  │   │   DAG drift_report  │\n              │  → promueve mejor     │   │   (lunes 06:00 UTC) │\n              └───────────────────────┘   │                     │\n                                          │  PSI + KS por feat. │\n                                          │  model decay (RMSE) │\n                                          │  → auto-retraining  │\n                                          └─────────────────────┘\n```\n\n### Servicios\n\n| Servicio | Puerto | Descripción |\n|---|---|---|\n| `postgres` | 5433 | Metadata Airflow + Feature Store (`featurestore`) + backend MLflow |\n| `redis` | — | Broker Celery para workers de Airflow |\n| `airflow-apiserver` | 8080 | UI y API REST de Airflow (credenciales: `airflow / airflow`) |\n| `mlflow` | 9090 | Experiment tracking y Model Registry |\n| `ray-serve` | 8265 (dashboard), 8001 (interno) | Inferencia distribuida (2 réplicas) |\n| `api` | 8000 | API REST pública (`/api/v1/forecast`, `/api/v1/wells`) |\n\n---\n\n## Quickstart\n\n### Requisitos\n- Docker \u003e= 24 y Docker Compose v2\n- 8 GB RAM disponibles (Airflow + Ray usan bastante)\n\n### 1. Clonar y configurar\n\n```bash\ngit clone \u003crepo-url\u003e\ncd mia204_01\ncp .env.example .env\n# Editar .env: completar AIRFLOW__CORE__FERNET_KEY, POSTGRES_PASSWORD, etc.\n```\n\nGenerar las claves necesarias:\n\n```bash\n# FERNET_KEY\npython -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\"\n# SECRET_KEY\npython -c \"import secrets, base64; print(base64.b64encode(secrets.token_bytes(16)).decode())\"\n```\n\n### 2. Levantar el stack\n\n```bash\ndocker compose up --build\n```\n\nEsperar ~2 minutos a que `airflow-init` termine y todos los servicios estén healthy.\n\n### 3. Ejecutar el pipeline\n\n**Opción A — Desde la UI de Airflow:**\n1. Ir a http://localhost:8080 (user: `airflow`, password: del `.env`)\n2. Activar el DAG `ml_pipeline` y triggerear manualmente\n\n**Opción B — Desde CLI:**\n```bash\ndocker compose exec airflow-worker airflow dags trigger ml_pipeline\n```\n\nEl pipeline descarga el CSV público del gobierno, aplica el shift temporal, persiste en el Feature Store y entrena 4 variantes del modelo. El de menor RMSE queda con el alias `production` en MLflow.\n\n### 4. Verificar\n\n```bash\n# Health check\ncurl http://localhost:8000/health\n\n# Predicción para un pozo\ncurl \"http://localhost:8000/api/v1/forecast?id_well=96688\u0026date_start=2022-01-01\u0026date_end=2022-06-01\"\n\n# Listado de pozos\ncurl \"http://localhost:8000/api/v1/wells?date_query=2022-01-01\"\n\n# Docs interactivos\nopen http://localhost:8000/docs\n```\n\n### 5. Entrenamiento standalone (reproducible)\n\n```bash\ndocker compose exec airflow-worker python /opt/airflow/scripts/train.py --date \"2023-06-30\"\n```\n\nUsa el Feature Store con point-in-time correctness: solo features con `fecha \u003c= 2023-06-30`.\n\n---\n\n## API Reference\n\n### `GET /api/v1/forecast`\n\nDevuelve predicciones mensuales de producción de gas para un pozo.\n\n**Parámetros:**\n\n| Parámetro | Tipo | Requerido | Descripción |\n|---|---|---|---|\n| `id_well` | string | ✅ | ID del pozo (ej: `96688`) |\n| `date_start` | YYYY-MM-DD | ✅ | Inicio del rango |\n| `date_end` | YYYY-MM-DD | ✅ | Fin del rango |\n\n**Respuesta:**\n\n```json\n{\n  \"id_well\": \"96688\",\n  \"data\": [\n    { \"date\": \"2022-01-01\", \"prod\": 61.5 },\n    { \"date\": \"2022-02-01\", \"prod\": 49.8 }\n  ]\n}\n```\n\n\u003e **Nota sobre el shift temporal:** cada punto usa las features del mes indicado en `date` para predecir la producción del mes siguiente. El modelo fue entrenado con este desplazamiento para evitar data leakage.\n\n### `GET /api/v1/wells`\n\nLista los pozos con datos disponibles hasta una fecha dada.\n\n**Parámetros:**\n\n| Parámetro | Tipo | Requerido | Descripción |\n|---|---|---|---|\n| `date_query` | YYYY-MM-DD | ✅ | Fecha de corte |\n| `limit` | int | — | Máximo de resultados (default: 100) |\n| `offset` | int | — | Paginación (default: 0) |\n\n**Respuesta:**\n\n```json\n[{ \"id_well\": \"96688\" }, { \"id_well\": \"96630\" }]\n```\n\n---\n\n## Pipeline de ML\n\n### DAG `ml_pipeline` — schedule: `0 2 1 * *` (mensual)\n\n```\nstart → download_dataset → preprocess → persist_features\n      → read_features_from_store → train_experiments → promote_model\n```\n\n| Task | Descripción |\n|---|---|\n| `download_dataset` | Descarga el CSV de pozos no convencionales del portal datos.gob.ar |\n| `preprocess` | Filtra fechas, codifica `tipoextraccion`, aplica **shift temporal** (-1 mes por pozo) |\n| `persist_features` | Guarda en el Feature Store (DELETE-then-INSERT idempotente por rango de fechas) |\n| `read_features_from_store` | Valida disponibilidad de datos; devuelve solo metadata para no saturar XCom |\n| `train_experiments` | Lee del Feature Store directamente, entrena 4 variantes de RandomForest, loguea en MLflow |\n| `promote_model` | Asigna alias `production` al modelo con menor RMSE de test |\n\n**Shift temporal:** en el preprocesado, `prod_gas` se desplaza -1 mes por pozo (`groupby('idpozo')['prod_gas'].shift(-1)`). Así el modelo aprende: *dadas las condiciones operativas de este mes, ¿cuánto gas producirá el pozo el mes siguiente?* El último mes de cada pozo se descarta (sin target futuro).\n\n### DAG `drift_report` — schedule: `0 6 * * 1` (lunes)\n\n```\ncargar_datos_referencia ─┐\n                          ├─→ calcular_drift ─┐\ncargar_datos_recientes  ─┘                    ├─→ decidir_retraining\n                          ──→ evaluar_decay ──┘\n```\n\n| Task | Descripción |\n|---|---|\n| `cargar_datos_referencia` | Lee todo el Feature Store (distribución de entrenamiento) |\n| `cargar_datos_recientes` | Consulta features de los últimos 90 días |\n| `calcular_drift` | PSI y KS test por feature; loguea en MLflow (`drift_monitoring`) |\n| `evaluar_model_decay` | RMSE sobre datos recientes vs baseline taggeado en MLflow |\n| `decidir_retraining` | Si hay drift (PSI \u003e 0.2 o KS p \u003c 0.05) o decay (RMSE \u003e 15%), dispara `ml_pipeline` vía API REST |\n\n---\n\n## Feature Store\n\nImplementado sobre PostgreSQL (tabla `features` en la base `featurestore`). La clase `FeatureStore` en `airflow/plugins/ml_pipeline/feature_store.py` es la **única interfaz** al Feature Store — ningún otro módulo ejecuta SQL sobre esa tabla directamente.\n\n### Schema\n\n```sql\nCREATE TABLE features (\n    id SERIAL PRIMARY KEY,\n    id_pozo VARCHAR NOT NULL,\n    fecha DATE NOT NULL,      -- primer día del mes\n    tipoextraccion INT,       -- codificado con TIPOEXTRACCION_MAP estático\n    prod_gas FLOAT,           -- target (producción del mes siguiente, post-shift)\n    prod_agua FLOAT,\n    tef FLOAT,\n    prod_pet FLOAT,\n    profundidad FLOAT\n);\n```\n\n### Métodos\n\n| Método | Uso |\n|---|---|\n| `save_features(df, fecha_min, fecha_max)` | DELETE-then-INSERT por rango. Idempotente. |\n| `get_training_features(cutoff_date)` | Point-in-time correctness para reproducibilidad |\n| `get_inference_features(id_pozo, start, end)` | Online store para la API |\n| `count()` | Utilidad para verificar estado |\n\n---\n\n## Experiment Tracking (MLflow)\n\nDisponible en http://localhost:9090\n\nCada run registra:\n- **Parámetros:** `n_estimators`, `max_depth`, `features`, `training_date`, tamaño del dataset\n- **Métricas:** `train_rmse`, `train_mae`, `train_r2`, `test_rmse`, `test_mae`, `test_r2`\n- **Artefactos:** modelo serializado, gráfico de feature importance, firma del modelo (input/output schema)\n- **Tags:** `feature_set` (all / reduced), `data_source=featurestore`\n\nEl modelo promovido recibe el alias `production` en el Model Registry y queda accesible como `models:/hydrocarbon_forecast@production`.\n\n---\n\n## Infraestructura de Inferencia (Ray Serve)\n\nRay Serve corre en un contenedor separado con 2 réplicas del deployment `HydrocarbonForecast`. Al iniciar, carga el modelo con alias `production` desde MLflow.\n\nEl endpoint `/api/v1/forecast` de la API principal envía un POST a `ray-serve:8001/predict` con las features del período solicitado. Si Ray Serve no está disponible, devuelve `503`.\n\nDashboard de Ray: http://localhost:8265\n\n---\n\n## Tests\n\n```bash\n# Tests unitarios de la API (sin DB)\ncd api \u0026\u0026 python -m pytest tests/ -v --ignore=tests/test_integration_api_postgres.py\n\n# Tests de integración contra Docker (requiere stack levantado)\ncd api \u0026\u0026 RUN_INTEGRATION=1 python -m pytest tests/test_integration_api_postgres.py -v\n\n# Tests del módulo de drift\ndocker compose exec airflow-worker python -m pytest /opt/airflow/tests/ -v\n```\n\n**Suite actual:** 35 tests (17 unitarios API + 15 drift + 3 integración)\n\n---\n\n## Estructura del Repositorio\n\n```\nmia204_01/\n├── airflow/\n│   ├── dags/\n│   │   ├── dag_pozos.py          # DAG ml_pipeline (entrenamiento mensual)\n│   │   └── dag_drift_report.py   # DAG drift_report (monitoreo semanal)\n│   ├── plugins/ml_pipeline/\n│   │   ├── config.py             # Constantes centralizadas (umbrales, schedules, features)\n│   │   ├── db.py                 # Engine SQLAlchemy (FEATURESTORE_DB_URL)\n│   │   ├── feature_store.py      # Clase FeatureStore — única interfaz a la tabla features\n│   │   ├── training.py           # train_and_log(), promote_best_model()\n│   │   └── drift.py              # calcular_psi(), calcular_ks(), detectar_model_decay()\n│   └── tests/\n│       ├── conftest.py\n│       └── test_drift.py         # 15 tests (PSI, KS, decay, reporte)\n├── api/\n│   ├── app/\n│   │   ├── api/v1/endpoints/\n│   │   │   ├── forecast.py       # GET /forecast → Ray Serve → predicción\n│   │   │   └── wells.py          # GET /wells\n│   │   ├── repositories/\n│   │   │   ├── base.py           # Interfaz abstracta WellRepository\n│   │   │   ├── postgres.py       # Implementación PostgreSQL\n│   │   │   └── fake.py           # Implementación para tests\n│   │   ├── core/\n│   │   │   ├── config.py         # Settings (pydantic-settings)\n│   │   │   ├── dependencies.py   # Inyección de dependencias FastAPI\n│   │   │   └── model.py          # MODEL_FEATURES\n│   │   ├── schemas.py            # Pydantic models (ForecastPoint, ForecastResponse, WellItem)\n│   │   └── main.py               # create_app()\n│   └── tests/\n│       ├── test_forecast.py      # 11 tests unitarios\n│       ├── test_wells.py         # 6 tests unitarios\n│       └── test_integration_api_postgres.py  # 3 tests integración\n├── ray/\n│   └── serve_app.py              # Deployment Ray Serve (HydrocarbonForecast)\n├── db/\n│   └── init-featurestore.sql     # Bootstrap automático de tablas (production, wells, features)\n├── scripts/\n│   └── train.py                  # Entrenamiento standalone: python train.py --date YYYY-MM-DD\n├── docker-compose.yaml\n├── .env.example\n└── README.md\n```\n\n---\n\n## Variables de Entorno\n\nCopiar `.env.example` a `.env` y completar:\n\n| Variable | Descripción |\n|---|---|\n| `AIRFLOW_UID` | UID del usuario host (Linux: `id -u`) |\n| `AIRFLOW__CORE__FERNET_KEY` | Clave Fernet para encriptar conexiones |\n| `AIRFLOW__API__SECRET_KEY` | Secret JWT para la API de Airflow |\n| `POSTGRES_PASSWORD` | Contraseña de PostgreSQL |\n| `FEATURESTORE_DB_URL` | URL SQLAlchemy al Feature Store |\n| `AIRFLOW_API_URL` | URL del API server de Airflow (para auto-trigger de drift) |\n| `AIRFLOW_API_USER` / `AIRFLOW_API_PASSWORD` | Credenciales para disparar retraining |\n| `MLFLOW_TRACKING_URI` | URL del tracking server MLflow |\n\n---\n\n## Equipo\n\n| Integrante | GitHub | Contribuciones principales |\n|---|---|---|\n| **Hernan** | [hernangm](https://github.com/hernangm) | Pipeline Airflow, Feature Store PostgreSQL, MLflow, Ray Serve |\n| **Matias** | [matiascaccia](https://github.com/matiascaccia) | Docker Compose, API FastAPI, repositorios SQLAlchemy, tests |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhernangm%2Fmia204_01","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhernangm%2Fmia204_01","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhernangm%2Fmia204_01/lists"}