Metadata-Version: 2.4
Name: nikodym
Version: 1.1.2
Summary: Librería de riesgo de crédito: scoring, ML, provisiones CMF e IFRS 9/ECL, forward-looking y stress testing.
Project-URL: Homepage, https://github.com/nexolabs-gh/nikodym
Project-URL: Source, https://github.com/nexolabs-gh/nikodym
Project-URL: Documentation, https://github.com/nexolabs-gh/nikodym#readme
Project-URL: Changelog, https://github.com/nexolabs-gh/nikodym/blob/main/CHANGELOG.md
Author-email: Nikodym <admin@nxlabs.cl>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: cmf,credit-risk,ecl,ifrs9,provisioning,scorecard
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: jinja2>=3.1
Requires-Dist: joblib>=1.3
Requires-Dist: numpy>=1.22
Requires-Dist: pandas<3,>=2.0
Requires-Dist: pandera>=0.24
Requires-Dist: pyarrow>=14
Requires-Dist: pydantic>=2.5
Requires-Dist: pyyaml>=6.0
Provides-Extra: ai
Requires-Dist: anthropic>=0.40; extra == 'ai'
Provides-Extra: all
Requires-Dist: anthropic>=0.40; extra == 'all'
Requires-Dist: catboost>=1.2; extra == 'all'
Requires-Dist: fastapi>=0.110; extra == 'all'
Requires-Dist: hydra-core>=1.3; extra == 'all'
Requires-Dist: lifelines>=0.28; extra == 'all'
Requires-Dist: lightgbm>=4.0; extra == 'all'
Requires-Dist: llvmlite>=0.43; extra == 'all'
Requires-Dist: matplotlib>=3.7; extra == 'all'
Requires-Dist: mlflow>=2.10; extra == 'all'
Requires-Dist: numba>=0.60; extra == 'all'
Requires-Dist: omegaconf>=2.3; extra == 'all'
Requires-Dist: openpyxl>=3.1; extra == 'all'
Requires-Dist: optbinning>=0.19; extra == 'all'
Requires-Dist: optuna>=3.5; extra == 'all'
Requires-Dist: pmdarima>=2.0; extra == 'all'
Requires-Dist: polars>=0.20; extra == 'all'
Requires-Dist: python-docx>=1.1; extra == 'all'
Requires-Dist: python-multipart>=0.0.9; extra == 'all'
Requires-Dist: scikit-learn<1.8,>=1.6; extra == 'all'
Requires-Dist: scikit-learn>=1.6; extra == 'all'
Requires-Dist: scipy>=1.10; extra == 'all'
Requires-Dist: shap>=0.44; extra == 'all'
Requires-Dist: statsmodels>=0.14; extra == 'all'
Requires-Dist: uvicorn>=0.29; extra == 'all'
Requires-Dist: xgboost>=2.0; extra == 'all'
Provides-Extra: catboost
Requires-Dist: catboost>=1.2; extra == 'catboost'
Requires-Dist: scikit-learn>=1.6; extra == 'catboost'
Provides-Extra: docx
Requires-Dist: python-docx>=1.1; extra == 'docx'
Provides-Extra: excel
Requires-Dist: openpyxl>=3.1; extra == 'excel'
Provides-Extra: explain
Requires-Dist: llvmlite>=0.43; extra == 'explain'
Requires-Dist: matplotlib>=3.7; extra == 'explain'
Requires-Dist: numba>=0.60; extra == 'explain'
Requires-Dist: shap>=0.44; extra == 'explain'
Provides-Extra: forecasting
Requires-Dist: pmdarima>=2.0; extra == 'forecasting'
Requires-Dist: statsmodels>=0.14; extra == 'forecasting'
Provides-Extra: lightgbm
Requires-Dist: lightgbm>=4.0; extra == 'lightgbm'
Requires-Dist: scikit-learn>=1.6; extra == 'lightgbm'
Provides-Extra: ml
Requires-Dist: scikit-learn>=1.6; extra == 'ml'
Provides-Extra: pdf
Requires-Dist: weasyprint>=63; extra == 'pdf'
Provides-Extra: polars
Requires-Dist: polars>=0.20; extra == 'polars'
Provides-Extra: report
Requires-Dist: matplotlib>=3.7; extra == 'report'
Provides-Extra: scoring
Requires-Dist: optbinning>=0.19; extra == 'scoring'
Requires-Dist: scikit-learn<1.8,>=1.6; extra == 'scoring'
Requires-Dist: scipy>=1.10; extra == 'scoring'
Requires-Dist: statsmodels>=0.14; extra == 'scoring'
Provides-Extra: survival
Requires-Dist: lifelines>=0.28; extra == 'survival'
Provides-Extra: sweep
Requires-Dist: hydra-core>=1.3; extra == 'sweep'
Requires-Dist: omegaconf>=2.3; extra == 'sweep'
Provides-Extra: tracking
Requires-Dist: mlflow>=2.10; extra == 'tracking'
Provides-Extra: tuning
Requires-Dist: optuna>=3.5; extra == 'tuning'
Provides-Extra: ui
Requires-Dist: fastapi>=0.110; extra == 'ui'
Requires-Dist: openpyxl>=3.1; extra == 'ui'
Requires-Dist: python-docx>=1.1; extra == 'ui'
Requires-Dist: python-multipart>=0.0.9; extra == 'ui'
Requires-Dist: uvicorn>=0.29; extra == 'ui'
Provides-Extra: xgboost
Requires-Dist: scikit-learn>=1.6; extra == 'xgboost'
Requires-Dist: xgboost>=2.0; extra == 'xgboost'
Description-Content-Type: text/markdown

# Nikodym RiskLib

[![PyPI](https://img.shields.io/pypi/v/nikodym.svg)](https://pypi.org/project/nikodym/)
[![Python](https://img.shields.io/pypi/pyversions/nikodym.svg)](https://pypi.org/project/nikodym/)
[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/nexolabs-gh/nikodym/blob/main/LICENSE)
[![CI](https://github.com/nexolabs-gh/nikodym/actions/workflows/ci.yml/badge.svg)](https://github.com/nexolabs-gh/nikodym/actions/workflows/ci.yml)

Librería Python **open-source (Apache-2.0)** de riesgo de crédito **integral**:
scoring/scorecards, backends ML, provisiones **CMF (Chile)** e **IFRS 9/ECL**, forward-looking
y stress testing. Todo en un motor **reproducible por construcción** y con gobernanza
(model card + audit-trail) automática. Paquete: `nikodym`.

> **Estado: 1.x (estable).** El pipeline de validación de scorecard (F1) es **API estable
> (SemVer 1.x)**: no rompe hasta un 2.0. Las superficies que aún crecen —modelado ML, provisiones
> CMF/IFRS 9, forward-looking, y los contratos transversales de resultados/métricas/orquestación—
> siguen marcadas como **experimentales** (fuera de la garantía SemVer 1.x).

## Qué hace

Los seis dominios **calculan** hoy: son motores deterministas, sin *stubs*, con más de 1.200 tests
sobre los cinco que no tienen interfaz (más de 3.700 en la suite completa). Lo que los separa no es
"hecho / no hecho", sino **superficie** (¿tiene UI, preset y capítulo en el informe, o hay que
escribir el config en Python?) y **garantía de API** (¿congelada bajo SemVer 1.x, o experimental?).
No existe CLI.

| Dominio | Superficie | Garantía |
|---|---|---|
| **Scorecard (F1)** — binning/WoE monotónico (optbinning), selección (IV/VIF), regresión logística, scorecard escalado (PDO/offset), calibración, desempeño (AUC/KS/Gini) y estabilidad (PSI/CSI) | UI, preset e informe | **estable** (SemVer 1.x) |
| **Provisiones** — motores **CMF (Chile)** e **IFRS 9/ECL** separados; la provisión es el **máximo** de ambos (piso prudencial) | Python | experimental |
| **Stress testing** — escenarios adversos, shocks macro en escala logit, sensibilidad y *reverse stress* por bisección | Python | experimental |
| **Markov** — matrices de transición (cohorte/duración), Chapman-Kolmogorov, Aalen-Johansen, *term-structure* de PD | Python | experimental |
| **Forward-looking** — ARIMA/auto-ARIMA, VAR/VECM, Ljung-Box y modelos satélite macro → PD/LGD | Python | experimental |
| **Survival** — Kaplan-Meier, Cox/AFT y *hazard* discreto sobre datos censurados | Python | experimental |

- **Backends ML (F2)**: XGBoost, LightGBM, CatBoost y tuning (Optuna) como *extras* selectivos,
  con explicabilidad (SHAP) opcional.
- **No hace** (por si lo estás buscando): *roll rates*, curvas de cosecha/*vintage*, ni CLI.
- **Informe de validación, no un log**: cada corrida produce un documento con portada, resumen
  ejecutivo, metodología (redactada con los parámetros que realmente se usaron), resultados,
  conclusiones y anexos técnicos. Sale en HTML y PDF, y también como **base editable** (`.qmd` de
  Quarto o `.docx` de Word) para que escribas tu documentación encima: los capítulos que solo puede
  escribir un humano (contexto de la cartera, conclusión que se firma) vienen como *placeholders*
  con guía, nunca inventados.
- **Reproducibilidad total**: `(datos + config + semilla) → resultado idéntico`, con *lineage
  bundle* (git SHA + hash de datos + config + semilla + `uv.lock`) en cada corrida.

## Instalación

```bash
pip install nikodym                 # núcleo base (config, Study, lineage)
pip install 'nikodym[scoring]'      # MVP scorecard (optbinning + statsmodels + sklearn>=1.6)
pip install 'nikodym[all]'          # todo lo redistribuible (sin copyleft)
```

Requiere Python ≥ 3.11. El núcleo base es liviano: `import nikodym` **no** arrastra el stack ML;
los backends pesados viven tras *extras* opcionales con import perezoso.

## Quickstart

El experimento es un `NikodymConfig` declarativo; `nikodym.run(config)` lo ejecuta de extremo a
extremo (binning → selección → modelo → scorecard → calibración → desempeño → estabilidad) y
devuelve un `Study` reproducible. Este ejemplo usa el **preset estándar F1** sobre un dataset
sintético de consumo, así corre sin rellenar ningún campo:

```python
from pathlib import Path
from tempfile import mkdtemp

import nikodym
from nikodym.core.config import NikodymConfig
from nikodym.ui.datasets import materialize
from nikodym.ui.presets import standard_preset

# 1. Materializa el dataset sintético de consumo (determinista) en un workdir temporal.
workdir = Path(mkdtemp(prefix="nikodym-quickstart-"))
preset = standard_preset()
data_path = materialize(preset["dataset_id"], workdir=workdir)

# 2. Toma el config F1 curado y apúntalo al archivo de datos recién materializado.
cfg_dict = preset["config"]
cfg_dict["data"]["load"]["source"] = str(data_path)
config = NikodymConfig.model_validate(cfg_dict)

# 3. Ejecuta la corrida completa y verifica el estado.
study = nikodym.run(config)
assert study.run_context.status == "done"

# 4. Accede a los resultados namespaced por dominio/clave.
scorecard = study.artifacts.get("scorecard", "scorecard")             # tabla del scorecard
metrics = study.artifacts.get("performance", "discriminant_metrics")  # AUC/KS/Gini por partición
print(metrics)
```

`nikodym.run` es *fail-loud pero no explosivo*: ante un fallo devuelve el `Study` **parcial** con
`study.run_context.status == "failed"` (el error vive en el audit-trail y el lineage, no se
silencia). El consumidor por código **debe** chequear `study.run_context.status` antes de usar los
resultados.

## Limitaciones que debes conocer antes de usarlo en serio

El motor las publica de sí mismo —cada fila afectada emite su código `FALTA-DATO`—, así que aquí se
dicen igual de claro:

- **Los parámetros normativos CMF no son oficiales.** Se transcribieron del compendio **con
  asistencia de IA y verificación visual**: no provienen de la CMF ni están validados por ella.
  **Requieren validación humana contra la norma vigente antes de cualquier uso productivo.** Quedan
  dos brechas abiertas y declaradas (`FALTA-DATO`): aforos y *haircuts* de garantías financieras, y
  las tablas del RAN 21-10.
- **La EAD de IFRS 9 se despliega constante en el tiempo.** El panel longitudinal está diferido; el
  motor no lo aplana en silencio: cada fila lo declara con el código `FALTA-DATO-IFRS-4`, y el
  config **rechaza** `exposure_profile_col` en vez de fingir que lo usa.
- **Experimental no es "beta marketinera"**: todo lo que no sea el pipeline de scorecard puede
  cambiar de firma dentro de la 1.x, y no está *battle-tested* en producción.

## Principios de diseño

- **Reproducibilidad total**: misma entrada → resultado byte-idéntico, con lineage completo.
- **Gobernanza por construcción** (SR 11-7): *model card* y *audit-trail* automáticos.
- **Config declarativo** (Pydantic v2): *el config ES el experimento*.
- **Núcleo liviano**: los backends pesados van tras *extras* con import perezoso.
- **CMF ≠ IFRS 9**: dos motores separados; la provisión es el máximo (piso prudencial).
- **Lo que falta se declara, no se disimula**: un dato ausente sale como `FALTA-DATO` en el
  resultado; una opción sin motor detrás se rechaza al validar el config, no al final de la corrida.

## Documentación

Guía completa (conceptos, referencia de `run`/`Study`/`NikodymConfig`) en el
[sitio de documentación](https://github.com/nexolabs-gh/nikodym#readme). El `CHANGELOG.md`
registra los cambios por versión.

## Desarrollo

El proyecto usa [uv](https://docs.astral.sh/uv/) + hatchling, con layout `src/`.

```bash
uv sync                              # entorno completo (grupo dev: test/lint/docs)
uv run ruff check . && uv run ruff format --check .
uv run mypy                          # type-check estricto de todo el paquete
uv run pytest                        # suite de tests
```

## Licencia

[Apache-2.0](LICENSE). Sin dependencias copyleft (GPL/LGPL/AGPL) en el wheel.
