From f6b0e6dbbc971eb8ad94cc3b9f7f753b744a7ce0 Mon Sep 17 00:00:00 2001 From: s6pa1rta3n-lab Date: Tue, 8 Sep 2026 17:23:11 -0400 Subject: [PATCH] feat(ml): implement Python ML pipeline against FeatureVector and ModelResult contract - Extract NUMERIC_FEATURE_SPECS dynamically from packages/chenai-mlflow - Implement contract validation and missing-value imputation - Implement Stellar Horizon REST ingestion client - Implement FeatureBuilder transforming ledger history to contract-compliant vectors - Implement CreditScoreModel (GBM) and FraudDetectModel (IForest) with SHA-256 digests - Implement ModelResultEmitter with Soroban model-attestation compatibility - Implement FileDropSink, SqliteSink, and StdoutSink - Implement CLI and programmatic PipelineRunner - Add unit tests and bidirectional cross-language contract verification tests - Add GitHub Actions CI workflow for Python ML pipeline Closes #364 --- .github/workflows/backend.yml | 40 + ml/README.md | 60 + ml/artifacts/credit_score_gbm.joblib | Bin 0 -> 40168 bytes .../evaluation_report.json | 1 + ml/artifacts/credit_score_report/metrics.json | 1 + ml/artifacts/fraud_detect_iforest.joblib | Bin 0 -> 860937 bytes .../evaluation_report.json | 1 + ml/artifacts/fraud_detect_report/metrics.json | 1 + ml/artifacts/manifest.json | 20 + ml/contract.py | 434 +++ ml/data/sample_dataset.json | 3402 +++++++++++++++++ ml/data/sample_vectors.jsonl | 200 + ml/emitter.py | 78 + ml/features.py | 192 + ml/ingest.py | 101 + ml/models.py | 158 + ml/pipeline.py | 223 ++ ml/pyproject.toml | 28 + ml/requirements.txt | 5 + ml/sinks.py | 151 + ml/tests/__init__.py | 6 + ml/tests/node_validator_harness.js | 48 + ml/tests/test_contract.py | 155 + ml/tests/test_cross_language.py | 143 + ml/tests/test_features.py | 94 + ml/tests/test_ingest.py | 71 + ml/tests/test_models.py | 86 + ml/tests/test_pipeline.py | 70 + ml/tests/test_sinks.py | 85 + ml/train.py | 163 + packages/chenai-mlflow/src/index.test.ts | 2 + 31 files changed, 6019 insertions(+) create mode 100644 ml/artifacts/credit_score_gbm.joblib create mode 100644 ml/artifacts/credit_score_report/evaluation_report.json create mode 100644 ml/artifacts/credit_score_report/metrics.json create mode 100644 ml/artifacts/fraud_detect_iforest.joblib create mode 100644 ml/artifacts/fraud_detect_report/evaluation_report.json create mode 100644 ml/artifacts/fraud_detect_report/metrics.json create mode 100644 ml/artifacts/manifest.json create mode 100644 ml/contract.py create mode 100644 ml/data/sample_dataset.json create mode 100644 ml/data/sample_vectors.jsonl create mode 100644 ml/emitter.py create mode 100644 ml/features.py create mode 100644 ml/ingest.py create mode 100644 ml/models.py create mode 100644 ml/pipeline.py create mode 100644 ml/pyproject.toml create mode 100644 ml/requirements.txt create mode 100644 ml/sinks.py create mode 100644 ml/tests/node_validator_harness.js create mode 100644 ml/tests/test_contract.py create mode 100644 ml/tests/test_cross_language.py create mode 100644 ml/tests/test_features.py create mode 100644 ml/tests/test_ingest.py create mode 100644 ml/tests/test_models.py create mode 100644 ml/tests/test_pipeline.py create mode 100644 ml/tests/test_sinks.py create mode 100644 ml/train.py diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 9232e4c9..b1a74f50 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -11,6 +11,7 @@ on: - "apps/backend/**" - "packages/**" - "tests/integration/**" + - "ml/**" pull_request: paths: - "package.json" @@ -20,6 +21,7 @@ on: - "apps/backend/**" - "packages/**" - "tests/integration/**" + - "ml/**" workflow_dispatch: concurrency: @@ -127,3 +129,41 @@ jobs: # so a missing build script must not red-X the whole pipeline. - name: Build packages run: pnpm --filter "./packages/**" run build --if-present || true + + ml-pipeline: + name: Python ML Pipeline + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.9 + + - name: Setup Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install JS dependencies and build chenai-mlflow + run: | + pnpm install --no-frozen-lockfile + pnpm --filter @chenaikit/chenai-mlflow run build + + - name: Setup Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install Python dependencies + run: | + pip install -r ml/requirements.txt + + - name: Run ML tests + run: | + PYTHONPATH=ml pytest ml/tests diff --git a/ml/README.md b/ml/README.md index 68da7880..eb31f08b 100644 --- a/ml/README.md +++ b/ml/README.md @@ -412,6 +412,66 @@ same change. ## Verifying the contract ```bash +# Build TypeScript contract package pnpm --filter @chenaikit/chenai-mlflow run build # tsc -p tsconfig.json pnpm --filter @chenaikit/chenai-mlflow run test # vitest run + +# Run Python ML test suite (unit and cross-language contract verification) +pytest ml/tests +``` + +## Python ML Pipeline + +### Architecture + +``` +Stellar Horizon API ──> ingest.py ──> features.py ──> contract.py (validate & impute) + │ + ▼ + models.py (GBM / IForest) + │ + ▼ + emitter.py (ModelResult + Soroban Attestation) + │ + ┌───────────────────────┼──────────────────────┐ + ▼ ▼ ▼ + FileDropSink (.jsonl) SqliteSink (DB) StdoutSink (CLI) +``` + +### Components + +1. **Contract Layer (`contract.py`)**: Loads feature specifications dynamically from `packages/chenai-mlflow/src/index.ts`. Provides strict validation and missing-value imputation adhering to `FeatureVector` and `ModelResult` schemas. +2. **Horizon Ingest (`ingest.py`)**: REST client for Stellar Horizon accounts, operations, payments, and transactions. +3. **Feature Builder (`features.py`)**: Transforms raw Horizon ledger history into contract-compliant `FeatureVector` payloads. +4. **Scoring Models (`models.py`)**: + - `CreditScoreModel`: Gradient Boosting classifier predicting default risk in `[0, 1]` with risk labels (`low`, `medium`, `high`). + - `FraudDetectModel`: Isolation Forest anomaly detector calibrated to anomaly risk in `[0, 1]`. + - Computes 32-byte SHA-256 artifact digests matching Soroban `BytesN<32>` in `contracts/model-attestation`. +5. **ModelResult Emitter (`emitter.py`)**: Validates input vectors, performs inference, tracks `imputedFields`, and formats contract-compliant `ModelResult` records and Soroban attestation payloads. +6. **Sinks (`sinks.py`)**: + - `FileDropSink`: Appends newline-delimited JSON (`.jsonl`) or writes atomic individual JSON files per `{subjectId}_{task}.json`. + - `SqliteSink`: Stores structured results in SQLite `model_results` table with indexed fields. + - `StdoutSink`: Streams newline-delimited JSON to stdout. +7. **Training Pipeline (`train.py`)**: Reproducible training on `ml/data/sample_dataset.json` with evaluation reports (`ml/evaluation_report.py`) and artifact manifest (`ml/artifacts/manifest.json`). + +### Training Models + +```bash +python3 ml/train.py +``` + +### Running Scoring Pipeline + +```bash +# Score single account via Horizon testnet +python3 ml/pipeline.py --mode single --subject-id GACC123 --task all --sink stdout + +# Score single FeatureVector from JSON file +python3 ml/pipeline.py --mode single --input-file vector.json --task credit-score --sink stdout + +# Batch score accounts from JSONL file into SQLite database +python3 ml/pipeline.py --mode batch --input-file ml/data/sample_vectors.jsonl --sink sqlite --output ml/output/scores.db + +# Batch score accounts into newline-delimited JSON +python3 ml/pipeline.py --mode batch --input-file ml/data/sample_vectors.jsonl --sink file --output ml/output/scores.jsonl ``` diff --git a/ml/artifacts/credit_score_gbm.joblib b/ml/artifacts/credit_score_gbm.joblib new file mode 100644 index 0000000000000000000000000000000000000000..7e3c04600fa27bd2416a6cbc260602af94ce74af GIT binary patch literal 40168 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 literal 0 HcmV?d00001 diff --git a/ml/artifacts/credit_score_report/evaluation_report.json b/ml/artifacts/credit_score_report/evaluation_report.json new file mode 100644 index 00000000..a965da77 --- /dev/null +++ b/ml/artifacts/credit_score_report/evaluation_report.json @@ -0,0 +1 @@ +{"code_version":"1.0.0","dataset":{"identifier":"sample_dataset.json","version":"1.0.0"},"evaluation":{"config_id":"eval-stratified-split","params":{"seed":42,"test_size":0.25}},"generated_at":"2026-09-08T21:18:22Z","metrics":{"accuracy":1.0,"f1_score":1.0,"precision":1.0,"recall":1.0,"roc_auc":1.0},"model":{"name":"credit-score-gbm","version":"1.0.0"},"report_id":"er-dfcce5a2c0283f5e","schema_version":"1.0"} \ No newline at end of file diff --git a/ml/artifacts/credit_score_report/metrics.json b/ml/artifacts/credit_score_report/metrics.json new file mode 100644 index 00000000..fd576d2c --- /dev/null +++ b/ml/artifacts/credit_score_report/metrics.json @@ -0,0 +1 @@ +{"accuracy":1.0,"f1_score":1.0,"precision":1.0,"recall":1.0,"roc_auc":1.0} \ No newline at end of file diff --git a/ml/artifacts/fraud_detect_iforest.joblib b/ml/artifacts/fraud_detect_iforest.joblib new file mode 100644 index 0000000000000000000000000000000000000000..25605f078cd3fd66fd44739349300323a4341cd4 GIT binary patch literal 860937 zcmdRX30O_t_y0APP-#+zBu&N!AxZ08MUkN*ifB?wsWea_86p%RV?|Jx~bM9Vyuf5jVYY%7beZ2Y|G3?9u@#ib%Y-?v} z=43y~(%#u}ow=RmBoiAe2PaEsmjGYBxy}xDW-c}k_H#O(91a+}EnrK4obLeE?>6hq zTpXMN7WxjdH(}p82k5Bz^0RME9L=1});R}^^&Pm*%+18vY@MT>rE`F9Uo%%1hXCI} z-yd38nz^{L3JlPKR>IT(a|Z_(XBQ_k$ACS)0_>ZgN%!~;aBwg;akgOb#Y)1n(adR` ziL;BDlMCyvzrD#?2Xp7|oH?1dRwd&#uk)+n+TK@D)apx;R;~#A#t? z=Is2v(gL&HSXElWdZ=%?hE-u_mh^r~)WXTe#nOo->j2-u&g)&xoGe$HSUNee()e;a zJKEW}uzn5j?cY(O0N(-MMYr10(ZxDIhue1us~X6U$&b_lI=y}Q|NfMvWvc+4UcRE= zA8xd?S!3;DV&!CJ@m;+pKYrBekUoQ9&BIE^`@Mkg&rR$dR)ad35Ubai1B!jjqGzuWVVC;h(LfC)?Y|FntkPg$+tyDhLxfR<%VO

}vi))&ai#eozJYa@nso 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