diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 9232e4c..b1a74f5 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 68da788..eb31f08 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 0000000..7e3c046 Binary files /dev/null and b/ml/artifacts/credit_score_gbm.joblib differ 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 0000000..a965da7 --- /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 0000000..fd576d2 --- /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 0000000..25605f0 Binary files /dev/null and b/ml/artifacts/fraud_detect_iforest.joblib differ diff --git a/ml/artifacts/fraud_detect_report/evaluation_report.json b/ml/artifacts/fraud_detect_report/evaluation_report.json new file mode 100644 index 0000000..a7e0fd0 --- /dev/null +++ b/ml/artifacts/fraud_detect_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-isolation-forest","params":{"contamination":0.1,"seed":42}},"generated_at":"2026-09-08T21:18:22Z","metrics":{"anomaly_rate":0.08,"mean_risk_score":0.4296},"model":{"name":"fraud-detect-iforest","version":"1.0.0"},"report_id":"er-ca764cedf3c9342e","schema_version":"1.0"} \ No newline at end of file diff --git a/ml/artifacts/fraud_detect_report/metrics.json b/ml/artifacts/fraud_detect_report/metrics.json new file mode 100644 index 0000000..24a757f --- /dev/null +++ b/ml/artifacts/fraud_detect_report/metrics.json @@ -0,0 +1 @@ +{"anomaly_rate":0.08,"mean_risk_score":0.4296} \ No newline at end of file diff --git a/ml/artifacts/manifest.json b/ml/artifacts/manifest.json new file mode 100644 index 0000000..de823c9 --- /dev/null +++ b/ml/artifacts/manifest.json @@ -0,0 +1,20 @@ +{ + "models": { + "credit-score-gbm": { + "version": "1.0.0", + "artifact": "credit_score_gbm.joblib", + "artifact_hash_sha256": "a7108f71280a656e101518cac9257fc3106f84707937d8252f38b2e1f69d53fd", + "report_id": "er-dfcce5a2c0283f5e", + "attestation_bytes32": "a7108f71280a656e101518cac9257fc3106f84707937d8252f38b2e1f69d53fd" + }, + "fraud-detect-iforest": { + "version": "1.0.0", + "artifact": "fraud_detect_iforest.joblib", + "artifact_hash_sha256": "5d3e9e037b4d32883db2e4e85c8c71ac5e74aff40f5d833de154da7f256a9e76", + "report_id": "er-ca764cedf3c9342e", + "attestation_bytes32": "5d3e9e037b4d32883db2e4e85c8c71ac5e74aff40f5d833de154da7f256a9e76" + } + }, + "random_state": 42, + "dataset": "sample_dataset.json" +} \ No newline at end of file diff --git a/ml/contract.py b/ml/contract.py new file mode 100644 index 0000000..a8991a9 --- /dev/null +++ b/ml/contract.py @@ -0,0 +1,434 @@ +"""Data contract definitions and validators for the chenai ML pipeline. + +Enforces schema validation and missing-value imputation for FeatureVector +and ModelResult payloads, referencing NUMERIC_FEATURE_SPECS in +packages/chenai-mlflow/src/index.ts as the single source of truth. +""" + +from __future__ import annotations + +import datetime +import math +import re +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Dict, List, Set, Tuple + +FEATURE_VECTOR_SCHEMA_VERSION = "1.0.0" +MODEL_RESULT_SCHEMA_VERSION = "1.0.0" +STROOPS_PER_XLM = 10_000_000 + +SUBJECT_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{1,128}$") +MODEL_ID_PATTERN = re.compile(r"^[A-Za-z0-9._-]{1,128}$") +SEMVER_PATTERN = re.compile(r"^\d+\.\d+\.\d+$") +ISO_UTC_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$") + +SUBJECT_KINDS: Set[str] = {"account", "transaction"} +KYC_TIERS: Set[int] = {0, 1, 2, 3} +MODEL_TASKS: Set[str] = {"credit-score", "fraud-detect"} +RISK_LABELS: Set[str] = {"low", "medium", "high"} + + +class SchemaValidationError(ValueError): + """Raised when a payload does not satisfy the documented contract.""" + + def __init__(self, schema: str, issues: List[str]) -> None: + super().__init__(f"{schema} validation failed: {'; '.join(issues)}") + self.schema = schema + self.issues = issues + + +@dataclass(frozen=True) +class NumericFeatureSpec: + """Declarative specification for a single numeric feature.""" + + name: str + unit: str + integer: bool + min: float + max: float + missing_default: float + description: str + + +_CACHED_SPECS: List[NumericFeatureSpec] | None = None + + +def load_numeric_feature_specs() -> List[NumericFeatureSpec]: + """Load NUMERIC_FEATURE_SPECS from packages/chenai-mlflow/src/index.ts. + + Parses the TypeScript source directly to guarantee schema consistency + across language boundaries without manual duplication. + """ + global _CACHED_SPECS + if _CACHED_SPECS is not None: + return _CACHED_SPECS + + current_dir = Path(__file__).resolve().parent + repo_root = current_dir.parent + ts_path = repo_root / "packages" / "chenai-mlflow" / "src" / "index.ts" + + if not ts_path.exists(): + fallback_path = repo_root / "packages" / "chenai-mlflow" / "dist" / "index.js" + if not fallback_path.exists(): + raise FileNotFoundError( + f"Cannot locate source of truth NUMERIC_FEATURE_SPECS at {ts_path}" + ) + target_path = fallback_path + else: + target_path = ts_path + + content = target_path.read_text(encoding="utf-8") + block_match = re.search( + r"export\s+const\s+NUMERIC_FEATURE_SPECS[^=]*=\s*\[(.*?)\]\s*as\s+const;", + content, + re.DOTALL, + ) + if not block_match: + block_match = re.search( + r"exports\.NUMERIC_FEATURE_SPECS\s*=\s*\[(.*?)\];", + content, + re.DOTALL, + ) + + if not block_match: + raise ValueError("Failed to extract NUMERIC_FEATURE_SPECS from TypeScript file") + + block = block_match.group(1) + pattern = re.compile( + r"\{\s*" + r"name:\s*\"([^\"]+)\",\s*" + r"unit:\s*\"([^\"]+)\",\s*" + r"integer:\s*(true|false),\s*" + r"min:\s*([0-9_]+(?:\.[0-9]+)?),\s*" + r"max:\s*([0-9_]+(?:\.[0-9]+)?|Number\.MAX_SAFE_INTEGER),\s*" + r"missingDefault:\s*([0-9_]+(?:\.[0-9]+)?),\s*" + r"description:\s*\"([^\"]+)\"", + re.MULTILINE | re.DOTALL, + ) + + specs: List[NumericFeatureSpec] = [] + for match in pattern.finditer(block): + name = match.group(1) + unit = match.group(2) + integer = match.group(3) == "true" + raw_max = match.group(5) + min_str = match.group(4).replace("_", "") + def_str = match.group(6).replace("_", "") + desc = match.group(7) + + min_val = float(min_str) if not integer else int(min_str) + if raw_max == "Number.MAX_SAFE_INTEGER": + max_val = 9007199254740991 + else: + max_str = raw_max.replace("_", "") + max_val = float(max_str) if not integer else int(max_str) + def_val = float(def_str) if not integer else int(def_str) + + specs.append( + NumericFeatureSpec( + name=name, + unit=unit, + integer=integer, + min=min_val, + max=max_val, + missing_default=def_val, + description=desc, + ) + ) + + if not specs: + raise ValueError("Extracted empty NUMERIC_FEATURE_SPECS from TypeScript file") + + _CACHED_SPECS = specs + return _CACHED_SPECS + + +def _check_timestamp(issues: List[str], record: Dict[str, Any], field: str) -> None: + val = record.get(field) + if not isinstance(val, str) or not ISO_UTC_PATTERN.match(val): + issues.append(f'{field} must be an ISO-8601 UTC timestamp ending in "Z"') + return + try: + clean_val = val[:-1] + datetime.datetime.fromisoformat(clean_val) + except ValueError: + issues.append(f"{field} is not a real calendar instant") + + +def _check_string( + issues: List[str], + record: Dict[str, Any], + field: str, + pattern: re.Pattern[str], + expectation: str, +) -> None: + val = record.get(field) + if not isinstance(val, str): + issues.append(f"{field} must be a string") + elif not pattern.match(val): + issues.append(f"{field} must {expectation}") + + +def validate_feature_vector(payload: Any) -> Dict[str, Any]: + """Validate an untrusted payload against the FeatureVector contract. + + Parameters: + payload: The input dictionary to validate. + + Returns: + The validated dictionary. + + Raises: + SchemaValidationError: If the payload violates contract constraints. + """ + if not isinstance(payload, dict): + raise SchemaValidationError( + "FeatureVector", ["payload must be a plain object"] + ) + + issues: List[str] = [] + specs = load_numeric_feature_specs() + spec_names = {spec.name for spec in specs} + known_fields = { + "schemaVersion", + "subjectId", + "subjectKind", + "observedAt", + "kycTier", + } | spec_names + + for key in payload.keys(): + if key not in known_fields: + issues.append(f'unknown field "{key}"') + + _check_string( + issues, + payload, + "schemaVersion", + SEMVER_PATTERN, + 'be a semver string such as "1.0.0"', + ) + schema_ver = payload.get("schemaVersion") + if ( + isinstance(schema_ver, str) + and SEMVER_PATTERN.match(schema_ver) + and schema_ver != FEATURE_VECTOR_SCHEMA_VERSION + ): + issues.append( + f"schemaVersion {schema_ver} is not supported by this build (expected {FEATURE_VECTOR_SCHEMA_VERSION})" + ) + + _check_string( + issues, + payload, + "subjectId", + SUBJECT_ID_PATTERN, + "be an opaque pseudonymous id matching [A-Za-z0-9_-]{1,128} and must not carry personal data", + ) + + subj_kind = payload.get("subjectKind") + if subj_kind not in SUBJECT_KINDS: + issues.append( + f'subjectKind must be one of {", ".join(repr(k) for k in sorted(SUBJECT_KINDS))}' + ) + + _check_timestamp(issues, payload, "observedAt") + + kyc_tier = payload.get("kycTier") + if kyc_tier not in KYC_TIERS: + issues.append( + f'kycTier must be one of {", ".join(str(k) for k in sorted(KYC_TIERS))}' + ) + + for spec in specs: + if spec.name not in payload: + issues.append( + f"{spec.name} is required; use an explicit null to signal a missing value" + ) + continue + + val = payload[spec.name] + if val is None: + continue + + if isinstance(val, bool): + issues.append( + f"{spec.name} must be a finite number or null (unit: {spec.unit})" + ) + continue + + if not isinstance(val, (int, float)) or not math.isfinite(val): + issues.append( + f"{spec.name} must be a finite number or null (unit: {spec.unit})" + ) + continue + + if spec.integer and isinstance(val, float) and not val.is_integer(): + issues.append(f"{spec.name} must be an integer (unit: {spec.unit})") + continue + + if val < spec.min or val > spec.max: + issues.append( + f"{spec.name} must be within [{spec.min}, {spec.max}], got {val}" + ) + + if issues: + raise SchemaValidationError("FeatureVector", issues) + + return payload + + +def impute_feature_vector(payload: Any) -> Tuple[Dict[str, Any], List[str]]: + """Apply the documented missing-value policy to a FeatureVector. + + Parameters: + payload: FeatureVector payload to validate and impute. + + Returns: + A tuple of (imputed_vector_dict, imputed_field_names_list). + + Raises: + SchemaValidationError: If payload is not a valid FeatureVector. + """ + valid_vector = validate_feature_vector(payload) + specs = load_numeric_feature_specs() + + imputed: Dict[str, Any] = dict(valid_vector) + imputed_fields: List[str] = [] + + for spec in specs: + if imputed[spec.name] is None: + imputed[spec.name] = spec.missing_default + imputed_fields.append(spec.name) + + imputed["imputedFields"] = imputed_fields + return imputed, imputed_fields + + +def validate_model_result(payload: Any) -> Dict[str, Any]: + """Validate a payload against the ModelResult contract. + + Parameters: + payload: The model result dictionary to validate. + + Returns: + The validated dictionary. + + Raises: + SchemaValidationError: If constraints are violated. + """ + if not isinstance(payload, dict): + raise SchemaValidationError( + "ModelResult", ["payload must be a plain object"] + ) + + issues: List[str] = [] + known_fields = { + "schemaVersion", + "modelId", + "modelVersion", + "featureVectorVersion", + "task", + "subjectId", + "scoredAt", + "score", + "label", + "confidence", + "imputedFields", + } + + for key in payload.keys(): + if key not in known_fields: + issues.append(f'unknown field "{key}"') + + _check_string( + issues, + payload, + "schemaVersion", + SEMVER_PATTERN, + 'be a semver string such as "1.0.0"', + ) + schema_ver = payload.get("schemaVersion") + if ( + isinstance(schema_ver, str) + and SEMVER_PATTERN.match(schema_ver) + and schema_ver != MODEL_RESULT_SCHEMA_VERSION + ): + issues.append( + f"schemaVersion {schema_ver} is not supported by this build (expected {MODEL_RESULT_SCHEMA_VERSION})" + ) + + _check_string( + issues, + payload, + "modelId", + MODEL_ID_PATTERN, + "match [A-Za-z0-9._-]{1,128}", + ) + _check_string( + issues, + payload, + "modelVersion", + SEMVER_PATTERN, + 'be a semver string such as "2.3.1"', + ) + _check_string( + issues, + payload, + "featureVectorVersion", + SEMVER_PATTERN, + 'be a semver string such as "1.0.0"', + ) + + task = payload.get("task") + if task not in MODEL_TASKS: + issues.append( + f'task must be one of {", ".join(repr(t) for k, t in enumerate(sorted(MODEL_TASKS)))}' + ) + + _check_string( + issues, + payload, + "subjectId", + SUBJECT_ID_PATTERN, + "be an opaque pseudonymous id matching [A-Za-z0-9_-]{1,128} and must not carry personal data", + ) + + _check_timestamp(issues, payload, "scoredAt") + + score = payload.get("score") + if isinstance(score, bool) or not isinstance(score, (int, float)) or not math.isfinite(score): + issues.append("score must be a finite number") + elif score < 0.0 or score > 1.0: + issues.append(f"score must be within [0, 1], got {score}") + + label = payload.get("label") + if label not in RISK_LABELS: + issues.append( + f'label must be one of {", ".join(repr(l) for l in sorted(RISK_LABELS))}' + ) + + confidence = payload.get("confidence") + if isinstance(confidence, bool) or not isinstance(confidence, (int, float)) or not math.isfinite(confidence): + issues.append("confidence must be a finite number") + elif confidence < 0.0 or confidence > 1.0: + issues.append(f"confidence must be within [0, 1], got {confidence}") + + specs = load_numeric_feature_specs() + spec_names = {spec.name for spec in specs} + + imputed = payload.get("imputedFields") + if not isinstance(imputed, list): + issues.append("imputedFields must be an array (empty when nothing was imputed)") + else: + for name in imputed: + if not isinstance(name, str) or name not in spec_names: + issues.append( + f'imputedFields contains "{name}", which is not a known feature name' + ) + + if issues: + raise SchemaValidationError("ModelResult", issues) + + return payload diff --git a/ml/data/sample_dataset.json b/ml/data/sample_dataset.json new file mode 100644 index 0000000..9ba36b7 --- /dev/null +++ b/ml/data/sample_dataset.json @@ -0,0 +1,3402 @@ +[ + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0000", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 11, + "transactionCount30d": 107, + "averageBalanceStroops": 419610, + "largestTransferStroops": 1090620971, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 19, + "disputeRatio90d": 0.198, + "crossBorderTransferRatio30d": 0.731, + "medianSettlementLatencySeconds": 7.67, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0001", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1309, + "transactionCount30d": 128, + "averageBalanceStroops": 236505587, + "largestTransferStroops": 60302251, + "distinctCounterparties30d": 23, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.101, + "medianSettlementLatencySeconds": 3.13, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0002", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1566, + "transactionCount30d": 186, + "averageBalanceStroops": 1901823908, + "largestTransferStroops": 251167269, + "distinctCounterparties30d": 47, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.162, + "medianSettlementLatencySeconds": 3.03, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0003", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1529, + "transactionCount30d": 128, + "averageBalanceStroops": 1561364854, + "largestTransferStroops": 93472422, + "distinctCounterparties30d": 23, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.02, + "medianSettlementLatencySeconds": 4.9, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0004", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 59, + "transactionCount30d": 12, + "averageBalanceStroops": 40515368, + "largestTransferStroops": 45503389, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 17, + "disputeRatio90d": 0.184, + "crossBorderTransferRatio30d": 0.489, + "medianSettlementLatencySeconds": 14.46, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0005", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 700, + "transactionCount30d": 180, + "averageBalanceStroops": 3801002945, + "largestTransferStroops": 319963575, + "distinctCounterparties30d": 22, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.014, + "medianSettlementLatencySeconds": 6.31, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0006", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 263, + "transactionCount30d": 79, + "averageBalanceStroops": 3821519026, + "largestTransferStroops": 214078714, + "distinctCounterparties30d": 27, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.127, + "medianSettlementLatencySeconds": 4.82, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0007", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 6, + "transactionCount30d": 157, + "averageBalanceStroops": 4479144, + "largestTransferStroops": 1967907436, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 9, + "disputeRatio90d": 0.526, + "crossBorderTransferRatio30d": 0.751, + "medianSettlementLatencySeconds": 7.56, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0008", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 34, + "transactionCount30d": 13, + "averageBalanceStroops": 18115891, + "largestTransferStroops": 95899313, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 10, + "disputeRatio90d": 0.221, + "crossBorderTransferRatio30d": 0.437, + "medianSettlementLatencySeconds": 24.4, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0009", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1783, + "transactionCount30d": 28, + "averageBalanceStroops": 3557645390, + "largestTransferStroops": 225373707, + "distinctCounterparties30d": 27, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.042, + "medianSettlementLatencySeconds": 7.71, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0010", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 535, + "transactionCount30d": 187, + "averageBalanceStroops": 2244181937, + "largestTransferStroops": 484960649, + "distinctCounterparties30d": 39, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.053, + "medianSettlementLatencySeconds": 4.23, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0011", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1629, + "transactionCount30d": 169, + "averageBalanceStroops": 2606254832, + "largestTransferStroops": 204345362, + "distinctCounterparties30d": 24, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.102, + "medianSettlementLatencySeconds": 3.45, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0012", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 60, + "transactionCount30d": 4, + "averageBalanceStroops": 10257007, + "largestTransferStroops": 94214382, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 16, + "disputeRatio90d": 0.199, + "crossBorderTransferRatio30d": 0.254, + "medianSettlementLatencySeconds": 19.9, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0013", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1183, + "transactionCount30d": 84, + "averageBalanceStroops": 4444277904, + "largestTransferStroops": 396934583, + "distinctCounterparties30d": 17, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.177, + "medianSettlementLatencySeconds": 6.75, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0014", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 2, + "transactionCount30d": 200, + "averageBalanceStroops": 7294150, + "largestTransferStroops": 839641189, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 5, + "disputeRatio90d": 0.768, + "crossBorderTransferRatio30d": 0.963, + "medianSettlementLatencySeconds": 3.37, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0015", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1139, + "transactionCount30d": 47, + "averageBalanceStroops": 2715507143, + "largestTransferStroops": 92054959, + "distinctCounterparties30d": 33, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.108, + "medianSettlementLatencySeconds": 6.89, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0016", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 43, + "transactionCount30d": 11, + "averageBalanceStroops": 32789774, + "largestTransferStroops": 12614124, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 14, + "disputeRatio90d": 0.27, + "crossBorderTransferRatio30d": 0.433, + "medianSettlementLatencySeconds": 12.69, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0017", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 593, + "transactionCount30d": 165, + "averageBalanceStroops": 4167116918, + "largestTransferStroops": 55984845, + "distinctCounterparties30d": 41, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.196, + "medianSettlementLatencySeconds": 5.66, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0018", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 362, + "transactionCount30d": 188, + "averageBalanceStroops": 2461388464, + "largestTransferStroops": 152301192, + "distinctCounterparties30d": 43, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.085, + "medianSettlementLatencySeconds": 4.06, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0019", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1560, + "transactionCount30d": 99, + "averageBalanceStroops": 1703828849, + "largestTransferStroops": 492976633, + "distinctCounterparties30d": 43, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.024, + "medianSettlementLatencySeconds": 4.12, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0020", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 6, + "transactionCount30d": 19, + "averageBalanceStroops": 37173044, + "largestTransferStroops": 40885476, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 10, + "disputeRatio90d": 0.052, + "crossBorderTransferRatio30d": 0.383, + "medianSettlementLatencySeconds": 6.47, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0021", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 15, + "transactionCount30d": 66, + "averageBalanceStroops": 5543670, + "largestTransferStroops": 652165001, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 20, + "disputeRatio90d": 0.295, + "crossBorderTransferRatio30d": 0.846, + "medianSettlementLatencySeconds": 5.85, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0022", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 597, + "transactionCount30d": 141, + "averageBalanceStroops": 3568173213, + "largestTransferStroops": 112225547, + "distinctCounterparties30d": 16, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.132, + "medianSettlementLatencySeconds": 4.77, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0023", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1056, + "transactionCount30d": 33, + "averageBalanceStroops": 522701550, + "largestTransferStroops": 226155655, + "distinctCounterparties30d": 31, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.05, + "medianSettlementLatencySeconds": 3.95, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0024", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 13, + "transactionCount30d": 14, + "averageBalanceStroops": 12313673, + "largestTransferStroops": 47385696, + "distinctCounterparties30d": 4, + "failedPaymentCount90d": 10, + "disputeRatio90d": 0.269, + "crossBorderTransferRatio30d": 0.13, + "medianSettlementLatencySeconds": 25.2, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0025", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 300, + "transactionCount30d": 32, + "averageBalanceStroops": 4458351784, + "largestTransferStroops": 60070070, + "distinctCounterparties30d": 25, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.081, + "medianSettlementLatencySeconds": 5.41, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0026", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1948, + "transactionCount30d": 35, + "averageBalanceStroops": 807111048, + "largestTransferStroops": 11157156, + "distinctCounterparties30d": 34, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.185, + "medianSettlementLatencySeconds": 6.93, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0027", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 966, + "transactionCount30d": 198, + "averageBalanceStroops": 3185540042, + "largestTransferStroops": 93105914, + "distinctCounterparties30d": 22, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.044, + "medianSettlementLatencySeconds": 3.29, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0028", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 12, + "transactionCount30d": 210, + "averageBalanceStroops": 959074, + "largestTransferStroops": 607679841, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 35, + "disputeRatio90d": 0.452, + "crossBorderTransferRatio30d": 0.956, + "medianSettlementLatencySeconds": 2.42, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0029", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1843, + "transactionCount30d": 67, + "averageBalanceStroops": 4689264169, + "largestTransferStroops": 46486210, + "distinctCounterparties30d": 25, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.024, + "medianSettlementLatencySeconds": 7.45, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0030", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1285, + "transactionCount30d": 172, + "averageBalanceStroops": 4565656446, + "largestTransferStroops": 54014506, + "distinctCounterparties30d": 36, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.117, + "medianSettlementLatencySeconds": 5.61, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0031", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 518, + "transactionCount30d": 191, + "averageBalanceStroops": 3176020365, + "largestTransferStroops": 138143547, + "distinctCounterparties30d": 26, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.026, + "medianSettlementLatencySeconds": 6.23, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0032", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 25, + "transactionCount30d": 3, + "averageBalanceStroops": 625149, + "largestTransferStroops": 71510041, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 6, + "disputeRatio90d": 0.068, + "crossBorderTransferRatio30d": 0.185, + "medianSettlementLatencySeconds": 11.63, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0033", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1904, + "transactionCount30d": 37, + "averageBalanceStroops": 3876436931, + "largestTransferStroops": 208388346, + "distinctCounterparties30d": 28, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.088, + "medianSettlementLatencySeconds": 5.72, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0034", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1352, + "transactionCount30d": 187, + "averageBalanceStroops": 2371782991, + "largestTransferStroops": 368556326, + "distinctCounterparties30d": 45, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.186, + "medianSettlementLatencySeconds": 3.52, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0035", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 5, + "transactionCount30d": 109, + "averageBalanceStroops": 1795765, + "largestTransferStroops": 1688038731, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 22, + "disputeRatio90d": 0.297, + "crossBorderTransferRatio30d": 0.763, + "medianSettlementLatencySeconds": 4.09, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0036", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 37, + "transactionCount30d": 16, + "averageBalanceStroops": 16852461, + "largestTransferStroops": 16818112, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 16, + "disputeRatio90d": 0.257, + "crossBorderTransferRatio30d": 0.118, + "medianSettlementLatencySeconds": 13.34, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0037", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1404, + "transactionCount30d": 87, + "averageBalanceStroops": 3130818620, + "largestTransferStroops": 311134582, + "distinctCounterparties30d": 10, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.015, + "medianSettlementLatencySeconds": 7.42, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0038", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1217, + "transactionCount30d": 29, + "averageBalanceStroops": 3684558315, + "largestTransferStroops": 322732442, + "distinctCounterparties30d": 45, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.086, + "medianSettlementLatencySeconds": 3.21, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0039", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1941, + "transactionCount30d": 30, + "averageBalanceStroops": 3960851904, + "largestTransferStroops": 122783981, + "distinctCounterparties30d": 25, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.021, + "medianSettlementLatencySeconds": 6.9, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0040", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 44, + "transactionCount30d": 5, + "averageBalanceStroops": 15887173, + "largestTransferStroops": 31810617, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 16, + "disputeRatio90d": 0.056, + "crossBorderTransferRatio30d": 0.395, + "medianSettlementLatencySeconds": 13.3, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0041", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1742, + "transactionCount30d": 191, + "averageBalanceStroops": 3582141802, + "largestTransferStroops": 153240225, + "distinctCounterparties30d": 20, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.022, + "medianSettlementLatencySeconds": 7.36, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0042", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 4, + "transactionCount30d": 152, + "averageBalanceStroops": 7722606, + "largestTransferStroops": 1250885745, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 19, + "disputeRatio90d": 0.256, + "crossBorderTransferRatio30d": 0.898, + "medianSettlementLatencySeconds": 4.59, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0043", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1870, + "transactionCount30d": 37, + "averageBalanceStroops": 1298832728, + "largestTransferStroops": 354392886, + "distinctCounterparties30d": 42, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.136, + "medianSettlementLatencySeconds": 7.22, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0044", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 6, + "transactionCount30d": 4, + "averageBalanceStroops": 17529855, + "largestTransferStroops": 33966966, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 11, + "disputeRatio90d": 0.06, + "crossBorderTransferRatio30d": 0.339, + "medianSettlementLatencySeconds": 13.64, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0045", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 993, + "transactionCount30d": 175, + "averageBalanceStroops": 596694873, + "largestTransferStroops": 492917925, + "distinctCounterparties30d": 46, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.051, + "medianSettlementLatencySeconds": 6.54, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0046", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1164, + "transactionCount30d": 157, + "averageBalanceStroops": 946259450, + "largestTransferStroops": 241551028, + "distinctCounterparties30d": 14, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.184, + "medianSettlementLatencySeconds": 6.12, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0047", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1574, + "transactionCount30d": 96, + "averageBalanceStroops": 2278074590, + "largestTransferStroops": 368035401, + "distinctCounterparties30d": 36, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.08, + "medianSettlementLatencySeconds": 4.48, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0048", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 17, + "transactionCount30d": 14, + "averageBalanceStroops": 44622658, + "largestTransferStroops": 60888017, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 12, + "disputeRatio90d": 0.152, + "crossBorderTransferRatio30d": 0.433, + "medianSettlementLatencySeconds": 12.6, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0049", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 7, + "transactionCount30d": 452, + "averageBalanceStroops": 9730529, + "largestTransferStroops": 1802795794, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 25, + "disputeRatio90d": 0.425, + "crossBorderTransferRatio30d": 0.833, + "medianSettlementLatencySeconds": 2.92, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0050", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1725, + "transactionCount30d": 63, + "averageBalanceStroops": 2929742424, + "largestTransferStroops": 162356666, + "distinctCounterparties30d": 42, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.127, + "medianSettlementLatencySeconds": 4.68, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0051", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1477, + "transactionCount30d": 99, + "averageBalanceStroops": 955256552, + "largestTransferStroops": 23113281, + "distinctCounterparties30d": 12, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.197, + "medianSettlementLatencySeconds": 6.06, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0052", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 34, + "transactionCount30d": 14, + "averageBalanceStroops": 42262839, + "largestTransferStroops": 87265240, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 15, + "disputeRatio90d": 0.174, + "crossBorderTransferRatio30d": 0.198, + "medianSettlementLatencySeconds": 21.4, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0053", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1928, + "transactionCount30d": 47, + "averageBalanceStroops": 3443290201, + "largestTransferStroops": 127489440, + "distinctCounterparties30d": 21, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.104, + "medianSettlementLatencySeconds": 3.25, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0054", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1978, + "transactionCount30d": 51, + "averageBalanceStroops": 2060488639, + "largestTransferStroops": 440257261, + "distinctCounterparties30d": 39, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.106, + "medianSettlementLatencySeconds": 5.79, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0055", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1646, + "transactionCount30d": 133, + "averageBalanceStroops": 2268005699, + "largestTransferStroops": 455908426, + "distinctCounterparties30d": 45, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.179, + "medianSettlementLatencySeconds": 6.72, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0056", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 8, + "transactionCount30d": 182, + "averageBalanceStroops": 4147994, + "largestTransferStroops": 1869367511, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 38, + "disputeRatio90d": 0.439, + "crossBorderTransferRatio30d": 0.772, + "medianSettlementLatencySeconds": 4.96, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0057", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 580, + "transactionCount30d": 89, + "averageBalanceStroops": 1542454200, + "largestTransferStroops": 489440131, + "distinctCounterparties30d": 44, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.028, + "medianSettlementLatencySeconds": 4.16, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0058", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1546, + "transactionCount30d": 74, + "averageBalanceStroops": 375860817, + "largestTransferStroops": 228828247, + "distinctCounterparties30d": 31, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.093, + "medianSettlementLatencySeconds": 3.31, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0059", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 897, + "transactionCount30d": 169, + "averageBalanceStroops": 4478856390, + "largestTransferStroops": 482798948, + "distinctCounterparties30d": 46, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.095, + "medianSettlementLatencySeconds": 7.71, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0060", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 53, + "transactionCount30d": 13, + "averageBalanceStroops": 28120042, + "largestTransferStroops": 82238741, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 10, + "disputeRatio90d": 0.172, + "crossBorderTransferRatio30d": 0.209, + "medianSettlementLatencySeconds": 17.14, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0061", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 788, + "transactionCount30d": 191, + "averageBalanceStroops": 4943124483, + "largestTransferStroops": 344071663, + "distinctCounterparties30d": 44, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.181, + "medianSettlementLatencySeconds": 5.96, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0062", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 271, + "transactionCount30d": 184, + "averageBalanceStroops": 1940877826, + "largestTransferStroops": 475387276, + "distinctCounterparties30d": 39, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.01, + "medianSettlementLatencySeconds": 4.9, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0063", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 8, + "transactionCount30d": 217, + "averageBalanceStroops": 5662319, + "largestTransferStroops": 1314167346, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 31, + "disputeRatio90d": 0.277, + "crossBorderTransferRatio30d": 0.725, + "medianSettlementLatencySeconds": 1.17, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0064", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 27, + "transactionCount30d": 8, + "averageBalanceStroops": 43630431, + "largestTransferStroops": 19209801, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 3, + "disputeRatio90d": 0.287, + "crossBorderTransferRatio30d": 0.18, + "medianSettlementLatencySeconds": 5.51, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0065", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 588, + "transactionCount30d": 52, + "averageBalanceStroops": 4886266225, + "largestTransferStroops": 127018823, + "distinctCounterparties30d": 39, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.051, + "medianSettlementLatencySeconds": 4.84, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0066", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1692, + "transactionCount30d": 61, + "averageBalanceStroops": 4243444172, + "largestTransferStroops": 68035074, + "distinctCounterparties30d": 47, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.186, + "medianSettlementLatencySeconds": 5.88, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0067", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 912, + "transactionCount30d": 70, + "averageBalanceStroops": 4721369670, + "largestTransferStroops": 380770791, + "distinctCounterparties30d": 50, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.02, + "medianSettlementLatencySeconds": 6.86, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0068", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 55, + "transactionCount30d": 19, + "averageBalanceStroops": 2756102, + "largestTransferStroops": 56600900, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 16, + "disputeRatio90d": 0.215, + "crossBorderTransferRatio30d": 0.128, + "medianSettlementLatencySeconds": 21.19, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0069", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1839, + "transactionCount30d": 127, + "averageBalanceStroops": 3631884396, + "largestTransferStroops": 66660748, + "distinctCounterparties30d": 37, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.127, + "medianSettlementLatencySeconds": 7.14, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0070", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 7, + "transactionCount30d": 140, + "averageBalanceStroops": 8753457, + "largestTransferStroops": 1896846668, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 44, + "disputeRatio90d": 0.666, + "crossBorderTransferRatio30d": 0.861, + "medianSettlementLatencySeconds": 5.35, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0071", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1791, + "transactionCount30d": 171, + "averageBalanceStroops": 1252750071, + "largestTransferStroops": 467260020, + "distinctCounterparties30d": 25, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.056, + "medianSettlementLatencySeconds": 5.25, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0072", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 41, + "transactionCount30d": 20, + "averageBalanceStroops": 44841369, + "largestTransferStroops": 60867083, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 3, + "disputeRatio90d": 0.174, + "crossBorderTransferRatio30d": 0.23, + "medianSettlementLatencySeconds": 17.19, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0073", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1733, + "transactionCount30d": 86, + "averageBalanceStroops": 1561744198, + "largestTransferStroops": 482618188, + "distinctCounterparties30d": 48, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.176, + "medianSettlementLatencySeconds": 5.78, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0074", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 275, + "transactionCount30d": 81, + "averageBalanceStroops": 3192697141, + "largestTransferStroops": 272303235, + "distinctCounterparties30d": 45, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.138, + "medianSettlementLatencySeconds": 6.23, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0075", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1017, + "transactionCount30d": 24, + "averageBalanceStroops": 499661102, + "largestTransferStroops": 128968663, + "distinctCounterparties30d": 35, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.049, + "medianSettlementLatencySeconds": 6.32, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0076", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 35, + "transactionCount30d": 18, + "averageBalanceStroops": 35629567, + "largestTransferStroops": 56138336, + "distinctCounterparties30d": 4, + "failedPaymentCount90d": 20, + "disputeRatio90d": 0.133, + "crossBorderTransferRatio30d": 0.381, + "medianSettlementLatencySeconds": 11.77, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0077", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 4, + "transactionCount30d": 111, + "averageBalanceStroops": 3231028, + "largestTransferStroops": 1177634051, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 39, + "disputeRatio90d": 0.766, + "crossBorderTransferRatio30d": 0.907, + "medianSettlementLatencySeconds": 2.72, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0078", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 666, + "transactionCount30d": 170, + "averageBalanceStroops": 4826723646, + "largestTransferStroops": 114216083, + "distinctCounterparties30d": 28, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.072, + "medianSettlementLatencySeconds": 4.51, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0079", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 661, + "transactionCount30d": 31, + "averageBalanceStroops": 4284564874, + "largestTransferStroops": 307082332, + "distinctCounterparties30d": 28, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.189, + "medianSettlementLatencySeconds": 6.19, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0080", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 11, + "transactionCount30d": 1, + "averageBalanceStroops": 38524797, + "largestTransferStroops": 48163378, + "distinctCounterparties30d": 4, + "failedPaymentCount90d": 18, + "disputeRatio90d": 0.16, + "crossBorderTransferRatio30d": 0.174, + "medianSettlementLatencySeconds": 6.28, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0081", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 333, + "transactionCount30d": 36, + "averageBalanceStroops": 1821025347, + "largestTransferStroops": 49772562, + "distinctCounterparties30d": 46, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.137, + "medianSettlementLatencySeconds": 3.76, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0082", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 274, + "transactionCount30d": 83, + "averageBalanceStroops": 4903712880, + "largestTransferStroops": 420407450, + "distinctCounterparties30d": 36, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.119, + "medianSettlementLatencySeconds": 6.09, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0083", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1022, + "transactionCount30d": 133, + "averageBalanceStroops": 1941810119, + "largestTransferStroops": 315280620, + "distinctCounterparties30d": 49, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.122, + "medianSettlementLatencySeconds": 6.7, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0084", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 11, + "transactionCount30d": 158, + "averageBalanceStroops": 4440025, + "largestTransferStroops": 1918258633, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 15, + "disputeRatio90d": 0.268, + "crossBorderTransferRatio30d": 0.866, + "medianSettlementLatencySeconds": 2.41, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0085", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1022, + "transactionCount30d": 196, + "averageBalanceStroops": 2650399650, + "largestTransferStroops": 166368642, + "distinctCounterparties30d": 12, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.058, + "medianSettlementLatencySeconds": 4.41, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0086", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 245, + "transactionCount30d": 195, + "averageBalanceStroops": 949627058, + "largestTransferStroops": 71620056, + "distinctCounterparties30d": 44, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.13, + "medianSettlementLatencySeconds": 7.54, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0087", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 246, + "transactionCount30d": 35, + "averageBalanceStroops": 1339573053, + "largestTransferStroops": 76761860, + "distinctCounterparties30d": 39, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.061, + "medianSettlementLatencySeconds": 5.01, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0088", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 37, + "transactionCount30d": 18, + "averageBalanceStroops": 33138036, + "largestTransferStroops": 68755000, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 4, + "disputeRatio90d": 0.272, + "crossBorderTransferRatio30d": 0.394, + "medianSettlementLatencySeconds": 20.09, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0089", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 287, + "transactionCount30d": 78, + "averageBalanceStroops": 4183595988, + "largestTransferStroops": 420577602, + "distinctCounterparties30d": 27, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.008, + "medianSettlementLatencySeconds": 6.78, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0090", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1162, + "transactionCount30d": 186, + "averageBalanceStroops": 1294673401, + "largestTransferStroops": 324246448, + "distinctCounterparties30d": 37, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.163, + "medianSettlementLatencySeconds": 7.85, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0091", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 6, + "transactionCount30d": 259, + "averageBalanceStroops": 5591986, + "largestTransferStroops": 1189467358, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 11, + "disputeRatio90d": 0.7, + "crossBorderTransferRatio30d": 0.799, + "medianSettlementLatencySeconds": 7.24, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0092", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 47, + "transactionCount30d": 13, + "averageBalanceStroops": 36916358, + "largestTransferStroops": 14925405, + "distinctCounterparties30d": 4, + "failedPaymentCount90d": 5, + "disputeRatio90d": 0.129, + "crossBorderTransferRatio30d": 0.229, + "medianSettlementLatencySeconds": 29.25, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0093", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1871, + "transactionCount30d": 151, + "averageBalanceStroops": 4399911083, + "largestTransferStroops": 476738113, + "distinctCounterparties30d": 44, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.083, + "medianSettlementLatencySeconds": 3.94, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0094", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1375, + "transactionCount30d": 147, + "averageBalanceStroops": 2786110098, + "largestTransferStroops": 418016814, + "distinctCounterparties30d": 13, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.053, + "medianSettlementLatencySeconds": 3.66, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0095", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 997, + "transactionCount30d": 198, + "averageBalanceStroops": 2181821325, + "largestTransferStroops": 25499162, + "distinctCounterparties30d": 50, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.16, + "medianSettlementLatencySeconds": 6.55, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0096", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 40, + "transactionCount30d": 1, + "averageBalanceStroops": 37060964, + "largestTransferStroops": 64759583, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 10, + "disputeRatio90d": 0.298, + "crossBorderTransferRatio30d": 0.465, + "medianSettlementLatencySeconds": 16.54, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0097", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1426, + "transactionCount30d": 59, + "averageBalanceStroops": 3177787850, + "largestTransferStroops": 283198582, + "distinctCounterparties30d": 27, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.167, + "medianSettlementLatencySeconds": 7.9, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0098", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 8, + "transactionCount30d": 332, + "averageBalanceStroops": 2426689, + "largestTransferStroops": 1323744701, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 43, + "disputeRatio90d": 0.456, + "crossBorderTransferRatio30d": 0.964, + "medianSettlementLatencySeconds": 8.78, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0099", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1682, + "transactionCount30d": 126, + "averageBalanceStroops": 111017705, + "largestTransferStroops": 399795655, + "distinctCounterparties30d": 29, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.116, + "medianSettlementLatencySeconds": 6.3, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0100", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 33, + "transactionCount30d": 18, + "averageBalanceStroops": 32503600, + "largestTransferStroops": 56321751, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 20, + "disputeRatio90d": 0.241, + "crossBorderTransferRatio30d": 0.251, + "medianSettlementLatencySeconds": 28.32, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0101", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1528, + "transactionCount30d": 81, + "averageBalanceStroops": 2555852189, + "largestTransferStroops": 135392880, + "distinctCounterparties30d": 36, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.064, + "medianSettlementLatencySeconds": 5.36, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0102", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 890, + "transactionCount30d": 189, + "averageBalanceStroops": 753287375, + "largestTransferStroops": 29874754, + "distinctCounterparties30d": 18, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.193, + "medianSettlementLatencySeconds": 4.66, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0103", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1890, + "transactionCount30d": 132, + "averageBalanceStroops": 4823225778, + "largestTransferStroops": 498789655, + "distinctCounterparties30d": 39, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.144, + "medianSettlementLatencySeconds": 5.05, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0104", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 9, + "transactionCount30d": 16, + "averageBalanceStroops": 17785377, + "largestTransferStroops": 55440919, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 15, + "disputeRatio90d": 0.212, + "crossBorderTransferRatio30d": 0.441, + "medianSettlementLatencySeconds": 26.31, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0105", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 7, + "transactionCount30d": 212, + "averageBalanceStroops": 8186400, + "largestTransferStroops": 1661823046, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 44, + "disputeRatio90d": 0.148, + "crossBorderTransferRatio30d": 0.889, + "medianSettlementLatencySeconds": 9.29, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0106", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1629, + "transactionCount30d": 43, + "averageBalanceStroops": 4817777292, + "largestTransferStroops": 350240773, + "distinctCounterparties30d": 16, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.033, + "medianSettlementLatencySeconds": 4.5, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0107", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 194, + "transactionCount30d": 103, + "averageBalanceStroops": 3519410162, + "largestTransferStroops": 167500627, + "distinctCounterparties30d": 32, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.086, + "medianSettlementLatencySeconds": 4.22, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0108", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 41, + "transactionCount30d": 6, + "averageBalanceStroops": 11407409, + "largestTransferStroops": 33497411, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 15, + "disputeRatio90d": 0.205, + "crossBorderTransferRatio30d": 0.196, + "medianSettlementLatencySeconds": 27.82, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0109", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 575, + "transactionCount30d": 138, + "averageBalanceStroops": 2839779525, + "largestTransferStroops": 256730032, + "distinctCounterparties30d": 26, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.002, + "medianSettlementLatencySeconds": 7.02, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0110", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1487, + "transactionCount30d": 159, + "averageBalanceStroops": 778500147, + "largestTransferStroops": 247151870, + "distinctCounterparties30d": 32, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.06, + "medianSettlementLatencySeconds": 7.81, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0111", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1035, + "transactionCount30d": 97, + "averageBalanceStroops": 2175362821, + "largestTransferStroops": 137348842, + "distinctCounterparties30d": 34, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.072, + "medianSettlementLatencySeconds": 4.48, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0112", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 1, + "transactionCount30d": 474, + "averageBalanceStroops": 6640518, + "largestTransferStroops": 1089442138, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 41, + "disputeRatio90d": 0.706, + "crossBorderTransferRatio30d": 0.933, + "medianSettlementLatencySeconds": 9.76, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0113", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1805, + "transactionCount30d": 93, + "averageBalanceStroops": 3544271529, + "largestTransferStroops": 127577164, + "distinctCounterparties30d": 50, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.124, + "medianSettlementLatencySeconds": 6.39, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0114", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1386, + "transactionCount30d": 44, + "averageBalanceStroops": 2876011793, + "largestTransferStroops": 175857076, + "distinctCounterparties30d": 38, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.116, + "medianSettlementLatencySeconds": 6.66, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0115", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1962, + "transactionCount30d": 95, + "averageBalanceStroops": 1884572192, + "largestTransferStroops": 117800886, + "distinctCounterparties30d": 18, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.175, + "medianSettlementLatencySeconds": 4.83, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0116", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 58, + "transactionCount30d": 6, + "averageBalanceStroops": 17244970, + "largestTransferStroops": 74672520, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 13, + "disputeRatio90d": 0.251, + "crossBorderTransferRatio30d": 0.287, + "medianSettlementLatencySeconds": 6.88, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0117", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1861, + "transactionCount30d": 193, + "averageBalanceStroops": 4308819875, + "largestTransferStroops": 464055608, + "distinctCounterparties30d": 45, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.018, + "medianSettlementLatencySeconds": 4.97, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0118", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1198, + "transactionCount30d": 51, + "averageBalanceStroops": 2053291127, + "largestTransferStroops": 371221985, + "distinctCounterparties30d": 26, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.076, + "medianSettlementLatencySeconds": 6.19, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0119", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 2, + "transactionCount30d": 395, + "averageBalanceStroops": 3922880, + "largestTransferStroops": 1512488868, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 44, + "disputeRatio90d": 0.719, + "crossBorderTransferRatio30d": 0.868, + "medianSettlementLatencySeconds": 9.24, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0120", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 46, + "transactionCount30d": 3, + "averageBalanceStroops": 42641235, + "largestTransferStroops": 72312933, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 16, + "disputeRatio90d": 0.079, + "crossBorderTransferRatio30d": 0.118, + "medianSettlementLatencySeconds": 5.93, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0121", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 337, + "transactionCount30d": 44, + "averageBalanceStroops": 2408745090, + "largestTransferStroops": 218652640, + "distinctCounterparties30d": 39, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.134, + "medianSettlementLatencySeconds": 6.72, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0122", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1302, + "transactionCount30d": 59, + "averageBalanceStroops": 3902823261, + "largestTransferStroops": 361608518, + "distinctCounterparties30d": 16, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.123, + "medianSettlementLatencySeconds": 7.7, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0123", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 167, + "transactionCount30d": 196, + "averageBalanceStroops": 1691414076, + "largestTransferStroops": 248039481, + "distinctCounterparties30d": 38, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.171, + "medianSettlementLatencySeconds": 3.5, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0124", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 39, + "transactionCount30d": 12, + "averageBalanceStroops": 4063447, + "largestTransferStroops": 63437398, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 9, + "disputeRatio90d": 0.292, + "crossBorderTransferRatio30d": 0.48, + "medianSettlementLatencySeconds": 25.6, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0125", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1457, + "transactionCount30d": 74, + "averageBalanceStroops": 191729577, + "largestTransferStroops": 432407131, + "distinctCounterparties30d": 31, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.198, + "medianSettlementLatencySeconds": 6.93, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0126", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 2, + "transactionCount30d": 474, + "averageBalanceStroops": 9298063, + "largestTransferStroops": 944867064, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 18, + "disputeRatio90d": 0.669, + "crossBorderTransferRatio30d": 0.77, + "medianSettlementLatencySeconds": 7.97, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0127", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 667, + "transactionCount30d": 57, + "averageBalanceStroops": 4341397494, + "largestTransferStroops": 300025967, + "distinctCounterparties30d": 26, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.022, + "medianSettlementLatencySeconds": 7.33, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0128", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 5, + "transactionCount30d": 12, + "averageBalanceStroops": 15967319, + "largestTransferStroops": 89034501, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 3, + "disputeRatio90d": 0.094, + "crossBorderTransferRatio30d": 0.121, + "medianSettlementLatencySeconds": 23.54, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0129", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 332, + "transactionCount30d": 36, + "averageBalanceStroops": 2145460840, + "largestTransferStroops": 427694716, + "distinctCounterparties30d": 33, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.119, + "medianSettlementLatencySeconds": 5.26, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0130", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1359, + "transactionCount30d": 31, + "averageBalanceStroops": 1395492409, + "largestTransferStroops": 355390898, + "distinctCounterparties30d": 11, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.2, + "medianSettlementLatencySeconds": 7.24, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0131", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1505, + "transactionCount30d": 47, + "averageBalanceStroops": 4007135299, + "largestTransferStroops": 49455025, + "distinctCounterparties30d": 15, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.122, + "medianSettlementLatencySeconds": 3.33, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0132", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 44, + "transactionCount30d": 19, + "averageBalanceStroops": 36799577, + "largestTransferStroops": 53645651, + "distinctCounterparties30d": 4, + "failedPaymentCount90d": 19, + "disputeRatio90d": 0.124, + "crossBorderTransferRatio30d": 0.302, + "medianSettlementLatencySeconds": 15.75, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0133", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 2, + "transactionCount30d": 486, + "averageBalanceStroops": 9249870, + "largestTransferStroops": 961758513, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 33, + "disputeRatio90d": 0.722, + "crossBorderTransferRatio30d": 0.824, + "medianSettlementLatencySeconds": 8.45, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0134", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 951, + "transactionCount30d": 44, + "averageBalanceStroops": 1442384949, + "largestTransferStroops": 177784815, + "distinctCounterparties30d": 26, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.191, + "medianSettlementLatencySeconds": 6.43, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0135", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 237, + "transactionCount30d": 43, + "averageBalanceStroops": 3672433066, + "largestTransferStroops": 60052528, + "distinctCounterparties30d": 37, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.149, + "medianSettlementLatencySeconds": 4.86, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0136", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 40, + "transactionCount30d": 2, + "averageBalanceStroops": 39357005, + "largestTransferStroops": 85374852, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 13, + "disputeRatio90d": 0.218, + "crossBorderTransferRatio30d": 0.264, + "medianSettlementLatencySeconds": 26.84, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0137", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1877, + "transactionCount30d": 33, + "averageBalanceStroops": 4259240421, + "largestTransferStroops": 332342714, + "distinctCounterparties30d": 29, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.021, + "medianSettlementLatencySeconds": 5.54, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0138", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1444, + "transactionCount30d": 143, + "averageBalanceStroops": 564794418, + "largestTransferStroops": 463856396, + "distinctCounterparties30d": 45, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.023, + "medianSettlementLatencySeconds": 4.39, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0139", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1753, + "transactionCount30d": 129, + "averageBalanceStroops": 4507726641, + "largestTransferStroops": 363170450, + "distinctCounterparties30d": 27, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.036, + "medianSettlementLatencySeconds": 6.51, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0140", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 15, + "transactionCount30d": 223, + "averageBalanceStroops": 5889195, + "largestTransferStroops": 513112080, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 14, + "disputeRatio90d": 0.496, + "crossBorderTransferRatio30d": 0.82, + "medianSettlementLatencySeconds": 2.28, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0141", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 287, + "transactionCount30d": 155, + "averageBalanceStroops": 1023988689, + "largestTransferStroops": 235395852, + "distinctCounterparties30d": 39, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.031, + "medianSettlementLatencySeconds": 4.56, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0142", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1690, + "transactionCount30d": 165, + "averageBalanceStroops": 2660761628, + "largestTransferStroops": 484240724, + "distinctCounterparties30d": 13, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.031, + "medianSettlementLatencySeconds": 6.09, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0143", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 657, + "transactionCount30d": 133, + "averageBalanceStroops": 2943005049, + "largestTransferStroops": 270737643, + "distinctCounterparties30d": 48, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.083, + "medianSettlementLatencySeconds": 4.08, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0144", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 27, + "transactionCount30d": 14, + "averageBalanceStroops": 7441686, + "largestTransferStroops": 48016697, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 18, + "disputeRatio90d": 0.182, + "crossBorderTransferRatio30d": 0.223, + "medianSettlementLatencySeconds": 10.51, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0145", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 212, + "transactionCount30d": 21, + "averageBalanceStroops": 977990647, + "largestTransferStroops": 123405378, + "distinctCounterparties30d": 18, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.058, + "medianSettlementLatencySeconds": 3.6, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0146", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1629, + "transactionCount30d": 130, + "averageBalanceStroops": 854603440, + "largestTransferStroops": 214122316, + "distinctCounterparties30d": 44, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.046, + "medianSettlementLatencySeconds": 5.79, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0147", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 2, + "transactionCount30d": 253, + "averageBalanceStroops": 709714, + "largestTransferStroops": 1436774457, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 34, + "disputeRatio90d": 0.745, + "crossBorderTransferRatio30d": 0.959, + "medianSettlementLatencySeconds": 6.18, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0148", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 50, + "transactionCount30d": 14, + "averageBalanceStroops": 19429036, + "largestTransferStroops": 25458308, + "distinctCounterparties30d": 4, + "failedPaymentCount90d": 3, + "disputeRatio90d": 0.292, + "crossBorderTransferRatio30d": 0.169, + "medianSettlementLatencySeconds": 28.69, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0149", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1803, + "transactionCount30d": 196, + "averageBalanceStroops": 4050381001, + "largestTransferStroops": 57314370, + "distinctCounterparties30d": 37, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.049, + "medianSettlementLatencySeconds": 5.94, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0150", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 910, + "transactionCount30d": 99, + "averageBalanceStroops": 3303207350, + "largestTransferStroops": 128955593, + "distinctCounterparties30d": 31, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.015, + "medianSettlementLatencySeconds": 6.17, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0151", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1955, + "transactionCount30d": 109, + "averageBalanceStroops": 734323009, + "largestTransferStroops": 65192761, + "distinctCounterparties30d": 19, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.039, + "medianSettlementLatencySeconds": 6.01, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0152", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 16, + "transactionCount30d": 16, + "averageBalanceStroops": 31135363, + "largestTransferStroops": 85663295, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 17, + "disputeRatio90d": 0.22, + "crossBorderTransferRatio30d": 0.453, + "medianSettlementLatencySeconds": 21.07, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0153", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 761, + "transactionCount30d": 180, + "averageBalanceStroops": 1457806226, + "largestTransferStroops": 246095867, + "distinctCounterparties30d": 14, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.088, + "medianSettlementLatencySeconds": 4.51, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0154", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 10, + "transactionCount30d": 78, + "averageBalanceStroops": 5904610, + "largestTransferStroops": 1589497668, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 24, + "disputeRatio90d": 0.423, + "crossBorderTransferRatio30d": 0.711, + "medianSettlementLatencySeconds": 4.32, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0155", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 257, + "transactionCount30d": 185, + "averageBalanceStroops": 3773457063, + "largestTransferStroops": 340180112, + "distinctCounterparties30d": 48, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.077, + "medianSettlementLatencySeconds": 5.9, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0156", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 33, + "transactionCount30d": 19, + "averageBalanceStroops": 43744071, + "largestTransferStroops": 35265073, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 18, + "disputeRatio90d": 0.175, + "crossBorderTransferRatio30d": 0.483, + "medianSettlementLatencySeconds": 16.26, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0157", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1562, + "transactionCount30d": 41, + "averageBalanceStroops": 841134891, + "largestTransferStroops": 143006398, + "distinctCounterparties30d": 38, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.105, + "medianSettlementLatencySeconds": 6.05, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0158", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 863, + "transactionCount30d": 92, + "averageBalanceStroops": 1763941470, + "largestTransferStroops": 425787616, + "distinctCounterparties30d": 31, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.12, + "medianSettlementLatencySeconds": 6.95, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0159", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 234, + "transactionCount30d": 104, + "averageBalanceStroops": 4800867235, + "largestTransferStroops": 374202137, + "distinctCounterparties30d": 34, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.05, + "medianSettlementLatencySeconds": 7.26, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0160", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 26, + "transactionCount30d": 3, + "averageBalanceStroops": 39106986, + "largestTransferStroops": 99068509, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 14, + "disputeRatio90d": 0.128, + "crossBorderTransferRatio30d": 0.362, + "medianSettlementLatencySeconds": 21.55, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0161", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 10, + "transactionCount30d": 412, + "averageBalanceStroops": 1421349, + "largestTransferStroops": 1164822039, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 29, + "disputeRatio90d": 0.55, + "crossBorderTransferRatio30d": 0.799, + "medianSettlementLatencySeconds": 2.15, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0162", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1423, + "transactionCount30d": 191, + "averageBalanceStroops": 1654039848, + "largestTransferStroops": 204668141, + "distinctCounterparties30d": 29, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.19, + "medianSettlementLatencySeconds": 4.71, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0163", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 493, + "transactionCount30d": 77, + "averageBalanceStroops": 4394866888, + "largestTransferStroops": 93185559, + "distinctCounterparties30d": 14, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.169, + "medianSettlementLatencySeconds": 3.51, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0164", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 47, + "transactionCount30d": 11, + "averageBalanceStroops": 41475389, + "largestTransferStroops": 27589104, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 15, + "disputeRatio90d": 0.089, + "crossBorderTransferRatio30d": 0.172, + "medianSettlementLatencySeconds": 22.32, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0165", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1756, + "transactionCount30d": 62, + "averageBalanceStroops": 3197457395, + "largestTransferStroops": 33457195, + "distinctCounterparties30d": 36, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.135, + "medianSettlementLatencySeconds": 4.19, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0166", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1350, + "transactionCount30d": 92, + "averageBalanceStroops": 3466396905, + "largestTransferStroops": 135629374, + "distinctCounterparties30d": 44, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.062, + "medianSettlementLatencySeconds": 7.04, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0167", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1951, + "transactionCount30d": 69, + "averageBalanceStroops": 4328136712, + "largestTransferStroops": 257862548, + "distinctCounterparties30d": 28, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.101, + "medianSettlementLatencySeconds": 5.09, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0168", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 14, + "transactionCount30d": 152, + "averageBalanceStroops": 2321942, + "largestTransferStroops": 1036877816, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 46, + "disputeRatio90d": 0.436, + "crossBorderTransferRatio30d": 0.811, + "medianSettlementLatencySeconds": 9.41, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0169", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 683, + "transactionCount30d": 41, + "averageBalanceStroops": 3377561834, + "largestTransferStroops": 156453713, + "distinctCounterparties30d": 38, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.029, + "medianSettlementLatencySeconds": 5.19, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0170", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1772, + "transactionCount30d": 135, + "averageBalanceStroops": 3899455941, + "largestTransferStroops": 24318450, + "distinctCounterparties30d": 36, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.079, + "medianSettlementLatencySeconds": 4.87, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0171", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 267, + "transactionCount30d": 115, + "averageBalanceStroops": 1064386641, + "largestTransferStroops": 181092636, + "distinctCounterparties30d": 16, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.13, + "medianSettlementLatencySeconds": 6.96, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0172", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 7, + "transactionCount30d": 10, + "averageBalanceStroops": 31705913, + "largestTransferStroops": 28628439, + "distinctCounterparties30d": 4, + "failedPaymentCount90d": 17, + "disputeRatio90d": 0.204, + "crossBorderTransferRatio30d": 0.462, + "medianSettlementLatencySeconds": 5.48, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0173", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1810, + "transactionCount30d": 58, + "averageBalanceStroops": 2366761348, + "largestTransferStroops": 69692990, + "distinctCounterparties30d": 28, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.06, + "medianSettlementLatencySeconds": 3.24, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0174", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1408, + "transactionCount30d": 179, + "averageBalanceStroops": 2063288053, + "largestTransferStroops": 69516376, + "distinctCounterparties30d": 41, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.107, + "medianSettlementLatencySeconds": 6.16, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0175", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 12, + "transactionCount30d": 123, + "averageBalanceStroops": 4886733, + "largestTransferStroops": 1421783504, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 44, + "disputeRatio90d": 0.347, + "crossBorderTransferRatio30d": 0.871, + "medianSettlementLatencySeconds": 2.69, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0176", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 38, + "transactionCount30d": 12, + "averageBalanceStroops": 4541882, + "largestTransferStroops": 80606503, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 19, + "disputeRatio90d": 0.247, + "crossBorderTransferRatio30d": 0.303, + "medianSettlementLatencySeconds": 5.51, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0177", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 189, + "transactionCount30d": 182, + "averageBalanceStroops": 1703321558, + "largestTransferStroops": 411212542, + "distinctCounterparties30d": 11, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.158, + "medianSettlementLatencySeconds": 4.72, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0178", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1680, + "transactionCount30d": 168, + "averageBalanceStroops": 1528156550, + "largestTransferStroops": 33802540, + "distinctCounterparties30d": 32, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.068, + "medianSettlementLatencySeconds": 6.21, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0179", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1524, + "transactionCount30d": 142, + "averageBalanceStroops": 2813284816, + "largestTransferStroops": 445669501, + "distinctCounterparties30d": 18, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.143, + "medianSettlementLatencySeconds": 7.72, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0180", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 23, + "transactionCount30d": 7, + "averageBalanceStroops": 2939789, + "largestTransferStroops": 36772387, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 13, + "disputeRatio90d": 0.283, + "crossBorderTransferRatio30d": 0.306, + "medianSettlementLatencySeconds": 29.73, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0181", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1069, + "transactionCount30d": 84, + "averageBalanceStroops": 4552252289, + "largestTransferStroops": 357302063, + "distinctCounterparties30d": 22, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.071, + "medianSettlementLatencySeconds": 6.9, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0182", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 5, + "transactionCount30d": 113, + "averageBalanceStroops": 6174337, + "largestTransferStroops": 1438345942, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 33, + "disputeRatio90d": 0.726, + "crossBorderTransferRatio30d": 0.816, + "medianSettlementLatencySeconds": 9.76, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0183", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1517, + "transactionCount30d": 147, + "averageBalanceStroops": 4749781016, + "largestTransferStroops": 237901521, + "distinctCounterparties30d": 15, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.12, + "medianSettlementLatencySeconds": 7.11, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0184", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 25, + "transactionCount30d": 4, + "averageBalanceStroops": 5373333, + "largestTransferStroops": 54012664, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 12, + "disputeRatio90d": 0.161, + "crossBorderTransferRatio30d": 0.387, + "medianSettlementLatencySeconds": 9.17, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0185", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 819, + "transactionCount30d": 134, + "averageBalanceStroops": 4576881314, + "largestTransferStroops": 477097648, + "distinctCounterparties30d": 32, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.2, + "medianSettlementLatencySeconds": 4.37, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0186", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 253, + "transactionCount30d": 191, + "averageBalanceStroops": 2838095498, + "largestTransferStroops": 205051179, + "distinctCounterparties30d": 42, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.136, + "medianSettlementLatencySeconds": 7.76, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0187", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1838, + "transactionCount30d": 175, + "averageBalanceStroops": 1981937953, + "largestTransferStroops": 77769228, + "distinctCounterparties30d": 14, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.156, + "medianSettlementLatencySeconds": 4.83, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0188", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 41, + "transactionCount30d": 2, + "averageBalanceStroops": 40607226, + "largestTransferStroops": 30594121, + "distinctCounterparties30d": 4, + "failedPaymentCount90d": 14, + "disputeRatio90d": 0.143, + "crossBorderTransferRatio30d": 0.405, + "medianSettlementLatencySeconds": 19.35, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0189", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 6, + "transactionCount30d": 253, + "averageBalanceStroops": 5274512, + "largestTransferStroops": 1894921070, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 20, + "disputeRatio90d": 0.772, + "crossBorderTransferRatio30d": 0.708, + "medianSettlementLatencySeconds": 2.67, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0190", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 1970, + "transactionCount30d": 163, + "averageBalanceStroops": 605779640, + "largestTransferStroops": 426055888, + "distinctCounterparties30d": 26, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.089, + "medianSettlementLatencySeconds": 7.98, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0191", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1521, + "transactionCount30d": 145, + "averageBalanceStroops": 959759428, + "largestTransferStroops": 82868638, + "distinctCounterparties30d": 14, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.035, + "medianSettlementLatencySeconds": 6.57, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0192", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 56, + "transactionCount30d": 11, + "averageBalanceStroops": 44817969, + "largestTransferStroops": 56630633, + "distinctCounterparties30d": 1, + "failedPaymentCount90d": 20, + "disputeRatio90d": 0.186, + "crossBorderTransferRatio30d": 0.457, + "medianSettlementLatencySeconds": 26.28, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0193", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1553, + "transactionCount30d": 199, + "averageBalanceStroops": 2838900744, + "largestTransferStroops": 436112500, + "distinctCounterparties30d": 33, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.045, + "medianSettlementLatencySeconds": 7.93, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0194", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 584, + "transactionCount30d": 146, + "averageBalanceStroops": 212889043, + "largestTransferStroops": 307437995, + "distinctCounterparties30d": 46, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.093, + "medianSettlementLatencySeconds": 7.92, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0195", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1353, + "transactionCount30d": 42, + "averageBalanceStroops": 382385977, + "largestTransferStroops": 223760508, + "distinctCounterparties30d": 40, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.082, + "medianSettlementLatencySeconds": 5.05, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0196", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 0, + "accountAgeDays": 3, + "transactionCount30d": 212, + "averageBalanceStroops": 1244282, + "largestTransferStroops": 1466312868, + "distinctCounterparties30d": 2, + "failedPaymentCount90d": 48, + "disputeRatio90d": 0.462, + "crossBorderTransferRatio30d": 0.739, + "medianSettlementLatencySeconds": 8.46, + "_target_label": 1 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0197", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 1672, + "transactionCount30d": 53, + "averageBalanceStroops": 4632963672, + "largestTransferStroops": 76658495, + "distinctCounterparties30d": 43, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.061, + "medianSettlementLatencySeconds": 3.81, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0198", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 808, + "transactionCount30d": 152, + "averageBalanceStroops": 3943677566, + "largestTransferStroops": 464157202, + "distinctCounterparties30d": 15, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.039, + "medianSettlementLatencySeconds": 7.78, + "_target_label": 0 + }, + { + "schemaVersion": "1.0.0", + "subjectId": "account-sample-0199", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 3, + "accountAgeDays": 356, + "transactionCount30d": 180, + "averageBalanceStroops": 2391270703, + "largestTransferStroops": 279816903, + "distinctCounterparties30d": 20, + "failedPaymentCount90d": 2, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.188, + "medianSettlementLatencySeconds": 3.77, + "_target_label": 0 + } +] \ No newline at end of file diff --git a/ml/data/sample_vectors.jsonl b/ml/data/sample_vectors.jsonl new file mode 100644 index 0000000..ab8270d --- /dev/null +++ b/ml/data/sample_vectors.jsonl @@ -0,0 +1,200 @@ +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0000", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 11, "transactionCount30d": 107, "averageBalanceStroops": 419610, "largestTransferStroops": 1090620971, "distinctCounterparties30d": 1, "failedPaymentCount90d": 19, "disputeRatio90d": 0.198, "crossBorderTransferRatio30d": 0.731, "medianSettlementLatencySeconds": 7.67} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0001", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1309, "transactionCount30d": 128, "averageBalanceStroops": 236505587, "largestTransferStroops": 60302251, "distinctCounterparties30d": 23, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.101, "medianSettlementLatencySeconds": 3.13} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0002", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1566, "transactionCount30d": 186, "averageBalanceStroops": 1901823908, "largestTransferStroops": 251167269, "distinctCounterparties30d": 47, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.162, "medianSettlementLatencySeconds": 3.03} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0003", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1529, "transactionCount30d": 128, "averageBalanceStroops": 1561364854, "largestTransferStroops": 93472422, "distinctCounterparties30d": 23, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.02, "medianSettlementLatencySeconds": 4.9} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0004", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 59, "transactionCount30d": 12, "averageBalanceStroops": 40515368, "largestTransferStroops": 45503389, "distinctCounterparties30d": 1, "failedPaymentCount90d": 17, "disputeRatio90d": 0.184, "crossBorderTransferRatio30d": 0.489, "medianSettlementLatencySeconds": 14.46} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0005", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 700, "transactionCount30d": 180, "averageBalanceStroops": 3801002945, "largestTransferStroops": 319963575, "distinctCounterparties30d": 22, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.014, "medianSettlementLatencySeconds": 6.31} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0006", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 263, "transactionCount30d": 79, "averageBalanceStroops": 3821519026, "largestTransferStroops": 214078714, "distinctCounterparties30d": 27, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.127, "medianSettlementLatencySeconds": 4.82} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0007", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 6, "transactionCount30d": 157, "averageBalanceStroops": 4479144, "largestTransferStroops": 1967907436, "distinctCounterparties30d": 3, "failedPaymentCount90d": 9, "disputeRatio90d": 0.526, "crossBorderTransferRatio30d": 0.751, "medianSettlementLatencySeconds": 7.56} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0008", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 34, "transactionCount30d": 13, "averageBalanceStroops": 18115891, "largestTransferStroops": 95899313, "distinctCounterparties30d": 5, "failedPaymentCount90d": 10, "disputeRatio90d": 0.221, "crossBorderTransferRatio30d": 0.437, "medianSettlementLatencySeconds": 24.4} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0009", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1783, "transactionCount30d": 28, "averageBalanceStroops": 3557645390, "largestTransferStroops": 225373707, "distinctCounterparties30d": 27, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.042, "medianSettlementLatencySeconds": 7.71} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0010", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 535, "transactionCount30d": 187, "averageBalanceStroops": 2244181937, "largestTransferStroops": 484960649, "distinctCounterparties30d": 39, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.053, "medianSettlementLatencySeconds": 4.23} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0011", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1629, "transactionCount30d": 169, "averageBalanceStroops": 2606254832, "largestTransferStroops": 204345362, "distinctCounterparties30d": 24, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.102, "medianSettlementLatencySeconds": 3.45} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0012", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 60, "transactionCount30d": 4, "averageBalanceStroops": 10257007, "largestTransferStroops": 94214382, "distinctCounterparties30d": 2, "failedPaymentCount90d": 16, "disputeRatio90d": 0.199, "crossBorderTransferRatio30d": 0.254, "medianSettlementLatencySeconds": 19.9} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0013", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1183, "transactionCount30d": 84, "averageBalanceStroops": 4444277904, "largestTransferStroops": 396934583, "distinctCounterparties30d": 17, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.177, "medianSettlementLatencySeconds": 6.75} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0014", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 2, "transactionCount30d": 200, "averageBalanceStroops": 7294150, "largestTransferStroops": 839641189, "distinctCounterparties30d": 2, "failedPaymentCount90d": 5, "disputeRatio90d": 0.768, "crossBorderTransferRatio30d": 0.963, "medianSettlementLatencySeconds": 3.37} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0015", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1139, "transactionCount30d": 47, "averageBalanceStroops": 2715507143, "largestTransferStroops": 92054959, "distinctCounterparties30d": 33, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.108, "medianSettlementLatencySeconds": 6.89} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0016", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 43, "transactionCount30d": 11, "averageBalanceStroops": 32789774, "largestTransferStroops": 12614124, "distinctCounterparties30d": 1, "failedPaymentCount90d": 14, "disputeRatio90d": 0.27, "crossBorderTransferRatio30d": 0.433, "medianSettlementLatencySeconds": 12.69} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0017", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 593, "transactionCount30d": 165, "averageBalanceStroops": 4167116918, "largestTransferStroops": 55984845, "distinctCounterparties30d": 41, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.196, "medianSettlementLatencySeconds": 5.66} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0018", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 362, "transactionCount30d": 188, "averageBalanceStroops": 2461388464, "largestTransferStroops": 152301192, "distinctCounterparties30d": 43, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.085, "medianSettlementLatencySeconds": 4.06} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0019", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1560, "transactionCount30d": 99, "averageBalanceStroops": 1703828849, "largestTransferStroops": 492976633, "distinctCounterparties30d": 43, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.024, "medianSettlementLatencySeconds": 4.12} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0020", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 6, "transactionCount30d": 19, "averageBalanceStroops": 37173044, "largestTransferStroops": 40885476, "distinctCounterparties30d": 5, "failedPaymentCount90d": 10, "disputeRatio90d": 0.052, "crossBorderTransferRatio30d": 0.383, "medianSettlementLatencySeconds": 6.47} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0021", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 15, "transactionCount30d": 66, "averageBalanceStroops": 5543670, "largestTransferStroops": 652165001, "distinctCounterparties30d": 3, "failedPaymentCount90d": 20, "disputeRatio90d": 0.295, "crossBorderTransferRatio30d": 0.846, "medianSettlementLatencySeconds": 5.85} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0022", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 597, "transactionCount30d": 141, "averageBalanceStroops": 3568173213, "largestTransferStroops": 112225547, "distinctCounterparties30d": 16, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.132, "medianSettlementLatencySeconds": 4.77} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0023", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1056, "transactionCount30d": 33, "averageBalanceStroops": 522701550, "largestTransferStroops": 226155655, "distinctCounterparties30d": 31, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.05, "medianSettlementLatencySeconds": 3.95} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0024", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 13, "transactionCount30d": 14, "averageBalanceStroops": 12313673, "largestTransferStroops": 47385696, "distinctCounterparties30d": 4, "failedPaymentCount90d": 10, "disputeRatio90d": 0.269, "crossBorderTransferRatio30d": 0.13, "medianSettlementLatencySeconds": 25.2} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0025", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 300, "transactionCount30d": 32, "averageBalanceStroops": 4458351784, "largestTransferStroops": 60070070, "distinctCounterparties30d": 25, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.081, "medianSettlementLatencySeconds": 5.41} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0026", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1948, "transactionCount30d": 35, "averageBalanceStroops": 807111048, "largestTransferStroops": 11157156, "distinctCounterparties30d": 34, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.185, "medianSettlementLatencySeconds": 6.93} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0027", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 966, "transactionCount30d": 198, "averageBalanceStroops": 3185540042, "largestTransferStroops": 93105914, "distinctCounterparties30d": 22, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.044, "medianSettlementLatencySeconds": 3.29} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0028", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 12, "transactionCount30d": 210, "averageBalanceStroops": 959074, "largestTransferStroops": 607679841, "distinctCounterparties30d": 3, "failedPaymentCount90d": 35, "disputeRatio90d": 0.452, "crossBorderTransferRatio30d": 0.956, "medianSettlementLatencySeconds": 2.42} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0029", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1843, "transactionCount30d": 67, "averageBalanceStroops": 4689264169, "largestTransferStroops": 46486210, "distinctCounterparties30d": 25, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.024, "medianSettlementLatencySeconds": 7.45} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0030", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1285, "transactionCount30d": 172, "averageBalanceStroops": 4565656446, "largestTransferStroops": 54014506, "distinctCounterparties30d": 36, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.117, "medianSettlementLatencySeconds": 5.61} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0031", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 518, "transactionCount30d": 191, "averageBalanceStroops": 3176020365, "largestTransferStroops": 138143547, "distinctCounterparties30d": 26, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.026, "medianSettlementLatencySeconds": 6.23} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0032", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 25, "transactionCount30d": 3, "averageBalanceStroops": 625149, "largestTransferStroops": 71510041, "distinctCounterparties30d": 5, "failedPaymentCount90d": 6, "disputeRatio90d": 0.068, "crossBorderTransferRatio30d": 0.185, "medianSettlementLatencySeconds": 11.63} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0033", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1904, "transactionCount30d": 37, "averageBalanceStroops": 3876436931, "largestTransferStroops": 208388346, "distinctCounterparties30d": 28, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.088, "medianSettlementLatencySeconds": 5.72} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0034", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1352, "transactionCount30d": 187, "averageBalanceStroops": 2371782991, "largestTransferStroops": 368556326, "distinctCounterparties30d": 45, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.186, "medianSettlementLatencySeconds": 3.52} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0035", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 5, "transactionCount30d": 109, "averageBalanceStroops": 1795765, "largestTransferStroops": 1688038731, "distinctCounterparties30d": 1, "failedPaymentCount90d": 22, "disputeRatio90d": 0.297, "crossBorderTransferRatio30d": 0.763, "medianSettlementLatencySeconds": 4.09} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0036", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 37, "transactionCount30d": 16, "averageBalanceStroops": 16852461, "largestTransferStroops": 16818112, "distinctCounterparties30d": 1, "failedPaymentCount90d": 16, "disputeRatio90d": 0.257, "crossBorderTransferRatio30d": 0.118, "medianSettlementLatencySeconds": 13.34} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0037", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1404, "transactionCount30d": 87, "averageBalanceStroops": 3130818620, "largestTransferStroops": 311134582, "distinctCounterparties30d": 10, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.015, "medianSettlementLatencySeconds": 7.42} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0038", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1217, "transactionCount30d": 29, "averageBalanceStroops": 3684558315, "largestTransferStroops": 322732442, "distinctCounterparties30d": 45, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.086, "medianSettlementLatencySeconds": 3.21} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0039", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1941, "transactionCount30d": 30, "averageBalanceStroops": 3960851904, "largestTransferStroops": 122783981, "distinctCounterparties30d": 25, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.021, "medianSettlementLatencySeconds": 6.9} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0040", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 44, "transactionCount30d": 5, "averageBalanceStroops": 15887173, "largestTransferStroops": 31810617, "distinctCounterparties30d": 2, "failedPaymentCount90d": 16, "disputeRatio90d": 0.056, "crossBorderTransferRatio30d": 0.395, "medianSettlementLatencySeconds": 13.3} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0041", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1742, "transactionCount30d": 191, "averageBalanceStroops": 3582141802, "largestTransferStroops": 153240225, "distinctCounterparties30d": 20, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.022, "medianSettlementLatencySeconds": 7.36} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0042", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 4, "transactionCount30d": 152, "averageBalanceStroops": 7722606, "largestTransferStroops": 1250885745, "distinctCounterparties30d": 2, "failedPaymentCount90d": 19, "disputeRatio90d": 0.256, "crossBorderTransferRatio30d": 0.898, "medianSettlementLatencySeconds": 4.59} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0043", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1870, "transactionCount30d": 37, "averageBalanceStroops": 1298832728, "largestTransferStroops": 354392886, "distinctCounterparties30d": 42, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.136, "medianSettlementLatencySeconds": 7.22} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0044", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 6, "transactionCount30d": 4, "averageBalanceStroops": 17529855, "largestTransferStroops": 33966966, "distinctCounterparties30d": 5, "failedPaymentCount90d": 11, "disputeRatio90d": 0.06, "crossBorderTransferRatio30d": 0.339, "medianSettlementLatencySeconds": 13.64} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0045", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 993, "transactionCount30d": 175, "averageBalanceStroops": 596694873, "largestTransferStroops": 492917925, "distinctCounterparties30d": 46, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.051, "medianSettlementLatencySeconds": 6.54} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0046", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1164, "transactionCount30d": 157, "averageBalanceStroops": 946259450, "largestTransferStroops": 241551028, "distinctCounterparties30d": 14, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.184, "medianSettlementLatencySeconds": 6.12} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0047", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1574, "transactionCount30d": 96, "averageBalanceStroops": 2278074590, "largestTransferStroops": 368035401, "distinctCounterparties30d": 36, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.08, "medianSettlementLatencySeconds": 4.48} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0048", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 17, "transactionCount30d": 14, "averageBalanceStroops": 44622658, "largestTransferStroops": 60888017, "distinctCounterparties30d": 2, "failedPaymentCount90d": 12, "disputeRatio90d": 0.152, "crossBorderTransferRatio30d": 0.433, "medianSettlementLatencySeconds": 12.6} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0049", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 7, "transactionCount30d": 452, "averageBalanceStroops": 9730529, "largestTransferStroops": 1802795794, "distinctCounterparties30d": 3, "failedPaymentCount90d": 25, "disputeRatio90d": 0.425, "crossBorderTransferRatio30d": 0.833, "medianSettlementLatencySeconds": 2.92} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0050", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1725, "transactionCount30d": 63, "averageBalanceStroops": 2929742424, "largestTransferStroops": 162356666, "distinctCounterparties30d": 42, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.127, "medianSettlementLatencySeconds": 4.68} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0051", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1477, "transactionCount30d": 99, "averageBalanceStroops": 955256552, "largestTransferStroops": 23113281, "distinctCounterparties30d": 12, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.197, "medianSettlementLatencySeconds": 6.06} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0052", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 34, "transactionCount30d": 14, "averageBalanceStroops": 42262839, "largestTransferStroops": 87265240, "distinctCounterparties30d": 2, "failedPaymentCount90d": 15, "disputeRatio90d": 0.174, "crossBorderTransferRatio30d": 0.198, "medianSettlementLatencySeconds": 21.4} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0053", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1928, "transactionCount30d": 47, "averageBalanceStroops": 3443290201, "largestTransferStroops": 127489440, "distinctCounterparties30d": 21, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.104, "medianSettlementLatencySeconds": 3.25} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0054", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1978, "transactionCount30d": 51, "averageBalanceStroops": 2060488639, "largestTransferStroops": 440257261, "distinctCounterparties30d": 39, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.106, "medianSettlementLatencySeconds": 5.79} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0055", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1646, "transactionCount30d": 133, "averageBalanceStroops": 2268005699, "largestTransferStroops": 455908426, "distinctCounterparties30d": 45, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.179, "medianSettlementLatencySeconds": 6.72} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0056", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 8, "transactionCount30d": 182, "averageBalanceStroops": 4147994, "largestTransferStroops": 1869367511, "distinctCounterparties30d": 2, "failedPaymentCount90d": 38, "disputeRatio90d": 0.439, "crossBorderTransferRatio30d": 0.772, "medianSettlementLatencySeconds": 4.96} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0057", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 580, "transactionCount30d": 89, "averageBalanceStroops": 1542454200, "largestTransferStroops": 489440131, "distinctCounterparties30d": 44, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.028, "medianSettlementLatencySeconds": 4.16} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0058", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1546, "transactionCount30d": 74, "averageBalanceStroops": 375860817, "largestTransferStroops": 228828247, "distinctCounterparties30d": 31, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.093, "medianSettlementLatencySeconds": 3.31} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0059", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 897, "transactionCount30d": 169, "averageBalanceStroops": 4478856390, "largestTransferStroops": 482798948, "distinctCounterparties30d": 46, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.095, "medianSettlementLatencySeconds": 7.71} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0060", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 53, "transactionCount30d": 13, "averageBalanceStroops": 28120042, "largestTransferStroops": 82238741, "distinctCounterparties30d": 5, "failedPaymentCount90d": 10, "disputeRatio90d": 0.172, "crossBorderTransferRatio30d": 0.209, "medianSettlementLatencySeconds": 17.14} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0061", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 788, "transactionCount30d": 191, "averageBalanceStroops": 4943124483, "largestTransferStroops": 344071663, "distinctCounterparties30d": 44, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.181, "medianSettlementLatencySeconds": 5.96} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0062", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 271, "transactionCount30d": 184, "averageBalanceStroops": 1940877826, "largestTransferStroops": 475387276, "distinctCounterparties30d": 39, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.01, "medianSettlementLatencySeconds": 4.9} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0063", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 8, "transactionCount30d": 217, "averageBalanceStroops": 5662319, "largestTransferStroops": 1314167346, "distinctCounterparties30d": 2, "failedPaymentCount90d": 31, "disputeRatio90d": 0.277, "crossBorderTransferRatio30d": 0.725, "medianSettlementLatencySeconds": 1.17} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0064", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 27, "transactionCount30d": 8, "averageBalanceStroops": 43630431, "largestTransferStroops": 19209801, "distinctCounterparties30d": 1, "failedPaymentCount90d": 3, "disputeRatio90d": 0.287, "crossBorderTransferRatio30d": 0.18, "medianSettlementLatencySeconds": 5.51} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0065", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 588, "transactionCount30d": 52, "averageBalanceStroops": 4886266225, "largestTransferStroops": 127018823, "distinctCounterparties30d": 39, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.051, "medianSettlementLatencySeconds": 4.84} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0066", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1692, "transactionCount30d": 61, "averageBalanceStroops": 4243444172, "largestTransferStroops": 68035074, "distinctCounterparties30d": 47, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.186, "medianSettlementLatencySeconds": 5.88} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0067", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 912, "transactionCount30d": 70, "averageBalanceStroops": 4721369670, "largestTransferStroops": 380770791, "distinctCounterparties30d": 50, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.02, "medianSettlementLatencySeconds": 6.86} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0068", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 55, "transactionCount30d": 19, "averageBalanceStroops": 2756102, "largestTransferStroops": 56600900, "distinctCounterparties30d": 5, "failedPaymentCount90d": 16, "disputeRatio90d": 0.215, "crossBorderTransferRatio30d": 0.128, "medianSettlementLatencySeconds": 21.19} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0069", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1839, "transactionCount30d": 127, "averageBalanceStroops": 3631884396, "largestTransferStroops": 66660748, "distinctCounterparties30d": 37, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.127, "medianSettlementLatencySeconds": 7.14} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0070", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 7, "transactionCount30d": 140, "averageBalanceStroops": 8753457, "largestTransferStroops": 1896846668, "distinctCounterparties30d": 2, "failedPaymentCount90d": 44, "disputeRatio90d": 0.666, "crossBorderTransferRatio30d": 0.861, "medianSettlementLatencySeconds": 5.35} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0071", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1791, "transactionCount30d": 171, "averageBalanceStroops": 1252750071, "largestTransferStroops": 467260020, "distinctCounterparties30d": 25, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.056, "medianSettlementLatencySeconds": 5.25} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0072", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 41, "transactionCount30d": 20, "averageBalanceStroops": 44841369, "largestTransferStroops": 60867083, "distinctCounterparties30d": 3, "failedPaymentCount90d": 3, "disputeRatio90d": 0.174, "crossBorderTransferRatio30d": 0.23, "medianSettlementLatencySeconds": 17.19} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0073", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1733, "transactionCount30d": 86, "averageBalanceStroops": 1561744198, "largestTransferStroops": 482618188, "distinctCounterparties30d": 48, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.176, "medianSettlementLatencySeconds": 5.78} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0074", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 275, "transactionCount30d": 81, "averageBalanceStroops": 3192697141, "largestTransferStroops": 272303235, "distinctCounterparties30d": 45, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.138, "medianSettlementLatencySeconds": 6.23} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0075", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1017, "transactionCount30d": 24, "averageBalanceStroops": 499661102, "largestTransferStroops": 128968663, "distinctCounterparties30d": 35, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.049, "medianSettlementLatencySeconds": 6.32} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0076", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 35, "transactionCount30d": 18, "averageBalanceStroops": 35629567, "largestTransferStroops": 56138336, "distinctCounterparties30d": 4, "failedPaymentCount90d": 20, "disputeRatio90d": 0.133, "crossBorderTransferRatio30d": 0.381, "medianSettlementLatencySeconds": 11.77} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0077", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 4, "transactionCount30d": 111, "averageBalanceStroops": 3231028, "largestTransferStroops": 1177634051, "distinctCounterparties30d": 1, "failedPaymentCount90d": 39, "disputeRatio90d": 0.766, "crossBorderTransferRatio30d": 0.907, "medianSettlementLatencySeconds": 2.72} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0078", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 666, "transactionCount30d": 170, "averageBalanceStroops": 4826723646, "largestTransferStroops": 114216083, "distinctCounterparties30d": 28, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.072, "medianSettlementLatencySeconds": 4.51} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0079", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 661, "transactionCount30d": 31, "averageBalanceStroops": 4284564874, "largestTransferStroops": 307082332, "distinctCounterparties30d": 28, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.189, "medianSettlementLatencySeconds": 6.19} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0080", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 11, "transactionCount30d": 1, "averageBalanceStroops": 38524797, "largestTransferStroops": 48163378, "distinctCounterparties30d": 4, "failedPaymentCount90d": 18, "disputeRatio90d": 0.16, "crossBorderTransferRatio30d": 0.174, "medianSettlementLatencySeconds": 6.28} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0081", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 333, "transactionCount30d": 36, "averageBalanceStroops": 1821025347, "largestTransferStroops": 49772562, "distinctCounterparties30d": 46, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.137, "medianSettlementLatencySeconds": 3.76} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0082", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 274, "transactionCount30d": 83, "averageBalanceStroops": 4903712880, "largestTransferStroops": 420407450, "distinctCounterparties30d": 36, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.119, "medianSettlementLatencySeconds": 6.09} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0083", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1022, "transactionCount30d": 133, "averageBalanceStroops": 1941810119, "largestTransferStroops": 315280620, "distinctCounterparties30d": 49, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.122, "medianSettlementLatencySeconds": 6.7} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0084", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 11, "transactionCount30d": 158, "averageBalanceStroops": 4440025, "largestTransferStroops": 1918258633, "distinctCounterparties30d": 1, "failedPaymentCount90d": 15, "disputeRatio90d": 0.268, "crossBorderTransferRatio30d": 0.866, "medianSettlementLatencySeconds": 2.41} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0085", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1022, "transactionCount30d": 196, "averageBalanceStroops": 2650399650, "largestTransferStroops": 166368642, "distinctCounterparties30d": 12, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.058, "medianSettlementLatencySeconds": 4.41} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0086", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 245, "transactionCount30d": 195, "averageBalanceStroops": 949627058, "largestTransferStroops": 71620056, "distinctCounterparties30d": 44, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.13, "medianSettlementLatencySeconds": 7.54} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0087", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 246, "transactionCount30d": 35, "averageBalanceStroops": 1339573053, "largestTransferStroops": 76761860, "distinctCounterparties30d": 39, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.061, "medianSettlementLatencySeconds": 5.01} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0088", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 37, "transactionCount30d": 18, "averageBalanceStroops": 33138036, "largestTransferStroops": 68755000, "distinctCounterparties30d": 1, "failedPaymentCount90d": 4, "disputeRatio90d": 0.272, "crossBorderTransferRatio30d": 0.394, "medianSettlementLatencySeconds": 20.09} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0089", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 287, "transactionCount30d": 78, "averageBalanceStroops": 4183595988, "largestTransferStroops": 420577602, "distinctCounterparties30d": 27, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.008, "medianSettlementLatencySeconds": 6.78} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0090", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1162, "transactionCount30d": 186, "averageBalanceStroops": 1294673401, "largestTransferStroops": 324246448, "distinctCounterparties30d": 37, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.163, "medianSettlementLatencySeconds": 7.85} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0091", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 6, "transactionCount30d": 259, "averageBalanceStroops": 5591986, "largestTransferStroops": 1189467358, "distinctCounterparties30d": 3, "failedPaymentCount90d": 11, "disputeRatio90d": 0.7, "crossBorderTransferRatio30d": 0.799, "medianSettlementLatencySeconds": 7.24} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0092", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 47, "transactionCount30d": 13, "averageBalanceStroops": 36916358, "largestTransferStroops": 14925405, "distinctCounterparties30d": 4, "failedPaymentCount90d": 5, "disputeRatio90d": 0.129, "crossBorderTransferRatio30d": 0.229, "medianSettlementLatencySeconds": 29.25} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0093", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1871, "transactionCount30d": 151, "averageBalanceStroops": 4399911083, "largestTransferStroops": 476738113, "distinctCounterparties30d": 44, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.083, "medianSettlementLatencySeconds": 3.94} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0094", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1375, "transactionCount30d": 147, "averageBalanceStroops": 2786110098, "largestTransferStroops": 418016814, "distinctCounterparties30d": 13, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.053, "medianSettlementLatencySeconds": 3.66} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0095", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 997, "transactionCount30d": 198, "averageBalanceStroops": 2181821325, "largestTransferStroops": 25499162, "distinctCounterparties30d": 50, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.16, "medianSettlementLatencySeconds": 6.55} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0096", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 40, "transactionCount30d": 1, "averageBalanceStroops": 37060964, "largestTransferStroops": 64759583, "distinctCounterparties30d": 1, "failedPaymentCount90d": 10, "disputeRatio90d": 0.298, "crossBorderTransferRatio30d": 0.465, "medianSettlementLatencySeconds": 16.54} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0097", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1426, "transactionCount30d": 59, "averageBalanceStroops": 3177787850, "largestTransferStroops": 283198582, "distinctCounterparties30d": 27, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.167, "medianSettlementLatencySeconds": 7.9} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0098", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 8, "transactionCount30d": 332, "averageBalanceStroops": 2426689, "largestTransferStroops": 1323744701, "distinctCounterparties30d": 1, "failedPaymentCount90d": 43, "disputeRatio90d": 0.456, "crossBorderTransferRatio30d": 0.964, "medianSettlementLatencySeconds": 8.78} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0099", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1682, "transactionCount30d": 126, "averageBalanceStroops": 111017705, "largestTransferStroops": 399795655, "distinctCounterparties30d": 29, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.116, "medianSettlementLatencySeconds": 6.3} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0100", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 33, "transactionCount30d": 18, "averageBalanceStroops": 32503600, "largestTransferStroops": 56321751, "distinctCounterparties30d": 3, "failedPaymentCount90d": 20, "disputeRatio90d": 0.241, "crossBorderTransferRatio30d": 0.251, "medianSettlementLatencySeconds": 28.32} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0101", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1528, "transactionCount30d": 81, "averageBalanceStroops": 2555852189, "largestTransferStroops": 135392880, "distinctCounterparties30d": 36, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.064, "medianSettlementLatencySeconds": 5.36} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0102", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 890, "transactionCount30d": 189, "averageBalanceStroops": 753287375, "largestTransferStroops": 29874754, "distinctCounterparties30d": 18, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.193, "medianSettlementLatencySeconds": 4.66} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0103", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1890, "transactionCount30d": 132, "averageBalanceStroops": 4823225778, "largestTransferStroops": 498789655, "distinctCounterparties30d": 39, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.144, "medianSettlementLatencySeconds": 5.05} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0104", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 9, "transactionCount30d": 16, "averageBalanceStroops": 17785377, "largestTransferStroops": 55440919, "distinctCounterparties30d": 5, "failedPaymentCount90d": 15, "disputeRatio90d": 0.212, "crossBorderTransferRatio30d": 0.441, "medianSettlementLatencySeconds": 26.31} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0105", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 7, "transactionCount30d": 212, "averageBalanceStroops": 8186400, "largestTransferStroops": 1661823046, "distinctCounterparties30d": 1, "failedPaymentCount90d": 44, "disputeRatio90d": 0.148, "crossBorderTransferRatio30d": 0.889, "medianSettlementLatencySeconds": 9.29} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0106", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1629, "transactionCount30d": 43, "averageBalanceStroops": 4817777292, "largestTransferStroops": 350240773, "distinctCounterparties30d": 16, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.033, "medianSettlementLatencySeconds": 4.5} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0107", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 194, "transactionCount30d": 103, "averageBalanceStroops": 3519410162, "largestTransferStroops": 167500627, "distinctCounterparties30d": 32, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.086, "medianSettlementLatencySeconds": 4.22} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0108", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 41, "transactionCount30d": 6, "averageBalanceStroops": 11407409, "largestTransferStroops": 33497411, "distinctCounterparties30d": 1, "failedPaymentCount90d": 15, "disputeRatio90d": 0.205, "crossBorderTransferRatio30d": 0.196, "medianSettlementLatencySeconds": 27.82} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0109", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 575, "transactionCount30d": 138, "averageBalanceStroops": 2839779525, "largestTransferStroops": 256730032, "distinctCounterparties30d": 26, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.002, "medianSettlementLatencySeconds": 7.02} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0110", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1487, "transactionCount30d": 159, "averageBalanceStroops": 778500147, "largestTransferStroops": 247151870, "distinctCounterparties30d": 32, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.06, "medianSettlementLatencySeconds": 7.81} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0111", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1035, "transactionCount30d": 97, "averageBalanceStroops": 2175362821, "largestTransferStroops": 137348842, "distinctCounterparties30d": 34, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.072, "medianSettlementLatencySeconds": 4.48} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0112", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 1, "transactionCount30d": 474, "averageBalanceStroops": 6640518, "largestTransferStroops": 1089442138, "distinctCounterparties30d": 1, "failedPaymentCount90d": 41, "disputeRatio90d": 0.706, "crossBorderTransferRatio30d": 0.933, "medianSettlementLatencySeconds": 9.76} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0113", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1805, "transactionCount30d": 93, "averageBalanceStroops": 3544271529, "largestTransferStroops": 127577164, "distinctCounterparties30d": 50, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.124, "medianSettlementLatencySeconds": 6.39} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0114", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1386, "transactionCount30d": 44, "averageBalanceStroops": 2876011793, "largestTransferStroops": 175857076, "distinctCounterparties30d": 38, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.116, "medianSettlementLatencySeconds": 6.66} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0115", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1962, "transactionCount30d": 95, "averageBalanceStroops": 1884572192, "largestTransferStroops": 117800886, "distinctCounterparties30d": 18, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.175, "medianSettlementLatencySeconds": 4.83} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0116", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 58, "transactionCount30d": 6, "averageBalanceStroops": 17244970, "largestTransferStroops": 74672520, "distinctCounterparties30d": 3, "failedPaymentCount90d": 13, "disputeRatio90d": 0.251, "crossBorderTransferRatio30d": 0.287, "medianSettlementLatencySeconds": 6.88} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0117", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1861, "transactionCount30d": 193, "averageBalanceStroops": 4308819875, "largestTransferStroops": 464055608, "distinctCounterparties30d": 45, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.018, "medianSettlementLatencySeconds": 4.97} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0118", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1198, "transactionCount30d": 51, "averageBalanceStroops": 2053291127, "largestTransferStroops": 371221985, "distinctCounterparties30d": 26, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.076, "medianSettlementLatencySeconds": 6.19} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0119", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 2, "transactionCount30d": 395, "averageBalanceStroops": 3922880, "largestTransferStroops": 1512488868, "distinctCounterparties30d": 1, "failedPaymentCount90d": 44, "disputeRatio90d": 0.719, "crossBorderTransferRatio30d": 0.868, "medianSettlementLatencySeconds": 9.24} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0120", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 46, "transactionCount30d": 3, "averageBalanceStroops": 42641235, "largestTransferStroops": 72312933, "distinctCounterparties30d": 3, "failedPaymentCount90d": 16, "disputeRatio90d": 0.079, "crossBorderTransferRatio30d": 0.118, "medianSettlementLatencySeconds": 5.93} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0121", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 337, "transactionCount30d": 44, "averageBalanceStroops": 2408745090, "largestTransferStroops": 218652640, "distinctCounterparties30d": 39, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.134, "medianSettlementLatencySeconds": 6.72} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0122", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1302, "transactionCount30d": 59, "averageBalanceStroops": 3902823261, "largestTransferStroops": 361608518, "distinctCounterparties30d": 16, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.123, "medianSettlementLatencySeconds": 7.7} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0123", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 167, "transactionCount30d": 196, "averageBalanceStroops": 1691414076, "largestTransferStroops": 248039481, "distinctCounterparties30d": 38, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.171, "medianSettlementLatencySeconds": 3.5} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0124", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 39, "transactionCount30d": 12, "averageBalanceStroops": 4063447, "largestTransferStroops": 63437398, "distinctCounterparties30d": 3, "failedPaymentCount90d": 9, "disputeRatio90d": 0.292, "crossBorderTransferRatio30d": 0.48, "medianSettlementLatencySeconds": 25.6} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0125", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1457, "transactionCount30d": 74, "averageBalanceStroops": 191729577, "largestTransferStroops": 432407131, "distinctCounterparties30d": 31, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.198, "medianSettlementLatencySeconds": 6.93} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0126", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 2, "transactionCount30d": 474, "averageBalanceStroops": 9298063, "largestTransferStroops": 944867064, "distinctCounterparties30d": 3, "failedPaymentCount90d": 18, "disputeRatio90d": 0.669, "crossBorderTransferRatio30d": 0.77, "medianSettlementLatencySeconds": 7.97} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0127", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 667, "transactionCount30d": 57, "averageBalanceStroops": 4341397494, "largestTransferStroops": 300025967, "distinctCounterparties30d": 26, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.022, "medianSettlementLatencySeconds": 7.33} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0128", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 5, "transactionCount30d": 12, "averageBalanceStroops": 15967319, "largestTransferStroops": 89034501, "distinctCounterparties30d": 3, "failedPaymentCount90d": 3, "disputeRatio90d": 0.094, "crossBorderTransferRatio30d": 0.121, "medianSettlementLatencySeconds": 23.54} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0129", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 332, "transactionCount30d": 36, "averageBalanceStroops": 2145460840, "largestTransferStroops": 427694716, "distinctCounterparties30d": 33, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.119, "medianSettlementLatencySeconds": 5.26} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0130", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1359, "transactionCount30d": 31, "averageBalanceStroops": 1395492409, "largestTransferStroops": 355390898, "distinctCounterparties30d": 11, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.2, "medianSettlementLatencySeconds": 7.24} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0131", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1505, "transactionCount30d": 47, "averageBalanceStroops": 4007135299, "largestTransferStroops": 49455025, "distinctCounterparties30d": 15, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.122, "medianSettlementLatencySeconds": 3.33} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0132", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 44, "transactionCount30d": 19, "averageBalanceStroops": 36799577, "largestTransferStroops": 53645651, "distinctCounterparties30d": 4, "failedPaymentCount90d": 19, "disputeRatio90d": 0.124, "crossBorderTransferRatio30d": 0.302, "medianSettlementLatencySeconds": 15.75} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0133", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 2, "transactionCount30d": 486, "averageBalanceStroops": 9249870, "largestTransferStroops": 961758513, "distinctCounterparties30d": 2, "failedPaymentCount90d": 33, "disputeRatio90d": 0.722, "crossBorderTransferRatio30d": 0.824, "medianSettlementLatencySeconds": 8.45} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0134", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 951, "transactionCount30d": 44, "averageBalanceStroops": 1442384949, "largestTransferStroops": 177784815, "distinctCounterparties30d": 26, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.191, "medianSettlementLatencySeconds": 6.43} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0135", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 237, "transactionCount30d": 43, "averageBalanceStroops": 3672433066, "largestTransferStroops": 60052528, "distinctCounterparties30d": 37, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.149, "medianSettlementLatencySeconds": 4.86} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0136", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 40, "transactionCount30d": 2, "averageBalanceStroops": 39357005, "largestTransferStroops": 85374852, "distinctCounterparties30d": 5, "failedPaymentCount90d": 13, "disputeRatio90d": 0.218, "crossBorderTransferRatio30d": 0.264, "medianSettlementLatencySeconds": 26.84} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0137", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1877, "transactionCount30d": 33, "averageBalanceStroops": 4259240421, "largestTransferStroops": 332342714, "distinctCounterparties30d": 29, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.021, "medianSettlementLatencySeconds": 5.54} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0138", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1444, "transactionCount30d": 143, "averageBalanceStroops": 564794418, "largestTransferStroops": 463856396, "distinctCounterparties30d": 45, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.023, "medianSettlementLatencySeconds": 4.39} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0139", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1753, "transactionCount30d": 129, "averageBalanceStroops": 4507726641, "largestTransferStroops": 363170450, "distinctCounterparties30d": 27, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.036, "medianSettlementLatencySeconds": 6.51} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0140", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 15, "transactionCount30d": 223, "averageBalanceStroops": 5889195, "largestTransferStroops": 513112080, "distinctCounterparties30d": 1, "failedPaymentCount90d": 14, "disputeRatio90d": 0.496, "crossBorderTransferRatio30d": 0.82, "medianSettlementLatencySeconds": 2.28} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0141", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 287, "transactionCount30d": 155, "averageBalanceStroops": 1023988689, "largestTransferStroops": 235395852, "distinctCounterparties30d": 39, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.031, "medianSettlementLatencySeconds": 4.56} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0142", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1690, "transactionCount30d": 165, "averageBalanceStroops": 2660761628, "largestTransferStroops": 484240724, "distinctCounterparties30d": 13, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.031, "medianSettlementLatencySeconds": 6.09} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0143", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 657, "transactionCount30d": 133, "averageBalanceStroops": 2943005049, "largestTransferStroops": 270737643, "distinctCounterparties30d": 48, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.083, "medianSettlementLatencySeconds": 4.08} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0144", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 27, "transactionCount30d": 14, "averageBalanceStroops": 7441686, "largestTransferStroops": 48016697, "distinctCounterparties30d": 5, "failedPaymentCount90d": 18, "disputeRatio90d": 0.182, "crossBorderTransferRatio30d": 0.223, "medianSettlementLatencySeconds": 10.51} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0145", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 212, "transactionCount30d": 21, "averageBalanceStroops": 977990647, "largestTransferStroops": 123405378, "distinctCounterparties30d": 18, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.058, "medianSettlementLatencySeconds": 3.6} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0146", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1629, "transactionCount30d": 130, "averageBalanceStroops": 854603440, "largestTransferStroops": 214122316, "distinctCounterparties30d": 44, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.046, "medianSettlementLatencySeconds": 5.79} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0147", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 2, "transactionCount30d": 253, "averageBalanceStroops": 709714, "largestTransferStroops": 1436774457, "distinctCounterparties30d": 1, "failedPaymentCount90d": 34, "disputeRatio90d": 0.745, "crossBorderTransferRatio30d": 0.959, "medianSettlementLatencySeconds": 6.18} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0148", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 50, "transactionCount30d": 14, "averageBalanceStroops": 19429036, "largestTransferStroops": 25458308, "distinctCounterparties30d": 4, "failedPaymentCount90d": 3, "disputeRatio90d": 0.292, "crossBorderTransferRatio30d": 0.169, "medianSettlementLatencySeconds": 28.69} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0149", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1803, "transactionCount30d": 196, "averageBalanceStroops": 4050381001, "largestTransferStroops": 57314370, "distinctCounterparties30d": 37, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.049, "medianSettlementLatencySeconds": 5.94} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0150", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 910, "transactionCount30d": 99, "averageBalanceStroops": 3303207350, "largestTransferStroops": 128955593, "distinctCounterparties30d": 31, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.015, "medianSettlementLatencySeconds": 6.17} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0151", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1955, "transactionCount30d": 109, "averageBalanceStroops": 734323009, "largestTransferStroops": 65192761, "distinctCounterparties30d": 19, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.039, "medianSettlementLatencySeconds": 6.01} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0152", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 16, "transactionCount30d": 16, "averageBalanceStroops": 31135363, "largestTransferStroops": 85663295, "distinctCounterparties30d": 5, "failedPaymentCount90d": 17, "disputeRatio90d": 0.22, "crossBorderTransferRatio30d": 0.453, "medianSettlementLatencySeconds": 21.07} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0153", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 761, "transactionCount30d": 180, "averageBalanceStroops": 1457806226, "largestTransferStroops": 246095867, "distinctCounterparties30d": 14, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.088, "medianSettlementLatencySeconds": 4.51} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0154", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 10, "transactionCount30d": 78, "averageBalanceStroops": 5904610, "largestTransferStroops": 1589497668, "distinctCounterparties30d": 1, "failedPaymentCount90d": 24, "disputeRatio90d": 0.423, "crossBorderTransferRatio30d": 0.711, "medianSettlementLatencySeconds": 4.32} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0155", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 257, "transactionCount30d": 185, "averageBalanceStroops": 3773457063, "largestTransferStroops": 340180112, "distinctCounterparties30d": 48, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.077, "medianSettlementLatencySeconds": 5.9} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0156", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 33, "transactionCount30d": 19, "averageBalanceStroops": 43744071, "largestTransferStroops": 35265073, "distinctCounterparties30d": 3, "failedPaymentCount90d": 18, "disputeRatio90d": 0.175, "crossBorderTransferRatio30d": 0.483, "medianSettlementLatencySeconds": 16.26} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0157", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1562, "transactionCount30d": 41, "averageBalanceStroops": 841134891, "largestTransferStroops": 143006398, "distinctCounterparties30d": 38, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.105, "medianSettlementLatencySeconds": 6.05} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0158", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 863, "transactionCount30d": 92, "averageBalanceStroops": 1763941470, "largestTransferStroops": 425787616, "distinctCounterparties30d": 31, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.12, "medianSettlementLatencySeconds": 6.95} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0159", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 234, "transactionCount30d": 104, "averageBalanceStroops": 4800867235, "largestTransferStroops": 374202137, "distinctCounterparties30d": 34, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.05, "medianSettlementLatencySeconds": 7.26} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0160", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 26, "transactionCount30d": 3, "averageBalanceStroops": 39106986, "largestTransferStroops": 99068509, "distinctCounterparties30d": 2, "failedPaymentCount90d": 14, "disputeRatio90d": 0.128, "crossBorderTransferRatio30d": 0.362, "medianSettlementLatencySeconds": 21.55} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0161", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 10, "transactionCount30d": 412, "averageBalanceStroops": 1421349, "largestTransferStroops": 1164822039, "distinctCounterparties30d": 3, "failedPaymentCount90d": 29, "disputeRatio90d": 0.55, "crossBorderTransferRatio30d": 0.799, "medianSettlementLatencySeconds": 2.15} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0162", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1423, "transactionCount30d": 191, "averageBalanceStroops": 1654039848, "largestTransferStroops": 204668141, "distinctCounterparties30d": 29, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.19, "medianSettlementLatencySeconds": 4.71} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0163", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 493, "transactionCount30d": 77, "averageBalanceStroops": 4394866888, "largestTransferStroops": 93185559, "distinctCounterparties30d": 14, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.169, "medianSettlementLatencySeconds": 3.51} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0164", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 47, "transactionCount30d": 11, "averageBalanceStroops": 41475389, "largestTransferStroops": 27589104, "distinctCounterparties30d": 5, "failedPaymentCount90d": 15, "disputeRatio90d": 0.089, "crossBorderTransferRatio30d": 0.172, "medianSettlementLatencySeconds": 22.32} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0165", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1756, "transactionCount30d": 62, "averageBalanceStroops": 3197457395, "largestTransferStroops": 33457195, "distinctCounterparties30d": 36, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.135, "medianSettlementLatencySeconds": 4.19} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0166", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1350, "transactionCount30d": 92, "averageBalanceStroops": 3466396905, "largestTransferStroops": 135629374, "distinctCounterparties30d": 44, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.062, "medianSettlementLatencySeconds": 7.04} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0167", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1951, "transactionCount30d": 69, "averageBalanceStroops": 4328136712, "largestTransferStroops": 257862548, "distinctCounterparties30d": 28, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.101, "medianSettlementLatencySeconds": 5.09} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0168", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 14, "transactionCount30d": 152, "averageBalanceStroops": 2321942, "largestTransferStroops": 1036877816, "distinctCounterparties30d": 1, "failedPaymentCount90d": 46, "disputeRatio90d": 0.436, "crossBorderTransferRatio30d": 0.811, "medianSettlementLatencySeconds": 9.41} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0169", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 683, "transactionCount30d": 41, "averageBalanceStroops": 3377561834, "largestTransferStroops": 156453713, "distinctCounterparties30d": 38, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.029, "medianSettlementLatencySeconds": 5.19} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0170", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1772, "transactionCount30d": 135, "averageBalanceStroops": 3899455941, "largestTransferStroops": 24318450, "distinctCounterparties30d": 36, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.079, "medianSettlementLatencySeconds": 4.87} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0171", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 267, "transactionCount30d": 115, "averageBalanceStroops": 1064386641, "largestTransferStroops": 181092636, "distinctCounterparties30d": 16, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.13, "medianSettlementLatencySeconds": 6.96} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0172", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 7, "transactionCount30d": 10, "averageBalanceStroops": 31705913, "largestTransferStroops": 28628439, "distinctCounterparties30d": 4, "failedPaymentCount90d": 17, "disputeRatio90d": 0.204, "crossBorderTransferRatio30d": 0.462, "medianSettlementLatencySeconds": 5.48} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0173", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1810, "transactionCount30d": 58, "averageBalanceStroops": 2366761348, "largestTransferStroops": 69692990, "distinctCounterparties30d": 28, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.06, "medianSettlementLatencySeconds": 3.24} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0174", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1408, "transactionCount30d": 179, "averageBalanceStroops": 2063288053, "largestTransferStroops": 69516376, "distinctCounterparties30d": 41, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.107, "medianSettlementLatencySeconds": 6.16} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0175", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 12, "transactionCount30d": 123, "averageBalanceStroops": 4886733, "largestTransferStroops": 1421783504, "distinctCounterparties30d": 1, "failedPaymentCount90d": 44, "disputeRatio90d": 0.347, "crossBorderTransferRatio30d": 0.871, "medianSettlementLatencySeconds": 2.69} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0176", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 38, "transactionCount30d": 12, "averageBalanceStroops": 4541882, "largestTransferStroops": 80606503, "distinctCounterparties30d": 5, "failedPaymentCount90d": 19, "disputeRatio90d": 0.247, "crossBorderTransferRatio30d": 0.303, "medianSettlementLatencySeconds": 5.51} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0177", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 189, "transactionCount30d": 182, "averageBalanceStroops": 1703321558, "largestTransferStroops": 411212542, "distinctCounterparties30d": 11, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.158, "medianSettlementLatencySeconds": 4.72} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0178", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1680, "transactionCount30d": 168, "averageBalanceStroops": 1528156550, "largestTransferStroops": 33802540, "distinctCounterparties30d": 32, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.068, "medianSettlementLatencySeconds": 6.21} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0179", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1524, "transactionCount30d": 142, "averageBalanceStroops": 2813284816, "largestTransferStroops": 445669501, "distinctCounterparties30d": 18, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.143, "medianSettlementLatencySeconds": 7.72} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0180", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 23, "transactionCount30d": 7, "averageBalanceStroops": 2939789, "largestTransferStroops": 36772387, "distinctCounterparties30d": 1, "failedPaymentCount90d": 13, "disputeRatio90d": 0.283, "crossBorderTransferRatio30d": 0.306, "medianSettlementLatencySeconds": 29.73} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0181", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1069, "transactionCount30d": 84, "averageBalanceStroops": 4552252289, "largestTransferStroops": 357302063, "distinctCounterparties30d": 22, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.071, "medianSettlementLatencySeconds": 6.9} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0182", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 5, "transactionCount30d": 113, "averageBalanceStroops": 6174337, "largestTransferStroops": 1438345942, "distinctCounterparties30d": 2, "failedPaymentCount90d": 33, "disputeRatio90d": 0.726, "crossBorderTransferRatio30d": 0.816, "medianSettlementLatencySeconds": 9.76} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0183", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1517, "transactionCount30d": 147, "averageBalanceStroops": 4749781016, "largestTransferStroops": 237901521, "distinctCounterparties30d": 15, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.12, "medianSettlementLatencySeconds": 7.11} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0184", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 25, "transactionCount30d": 4, "averageBalanceStroops": 5373333, "largestTransferStroops": 54012664, "distinctCounterparties30d": 3, "failedPaymentCount90d": 12, "disputeRatio90d": 0.161, "crossBorderTransferRatio30d": 0.387, "medianSettlementLatencySeconds": 9.17} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0185", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 819, "transactionCount30d": 134, "averageBalanceStroops": 4576881314, "largestTransferStroops": 477097648, "distinctCounterparties30d": 32, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.2, "medianSettlementLatencySeconds": 4.37} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0186", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 253, "transactionCount30d": 191, "averageBalanceStroops": 2838095498, "largestTransferStroops": 205051179, "distinctCounterparties30d": 42, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.136, "medianSettlementLatencySeconds": 7.76} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0187", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1838, "transactionCount30d": 175, "averageBalanceStroops": 1981937953, "largestTransferStroops": 77769228, "distinctCounterparties30d": 14, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.156, "medianSettlementLatencySeconds": 4.83} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0188", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 41, "transactionCount30d": 2, "averageBalanceStroops": 40607226, "largestTransferStroops": 30594121, "distinctCounterparties30d": 4, "failedPaymentCount90d": 14, "disputeRatio90d": 0.143, "crossBorderTransferRatio30d": 0.405, "medianSettlementLatencySeconds": 19.35} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0189", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 1, "accountAgeDays": 6, "transactionCount30d": 253, "averageBalanceStroops": 5274512, "largestTransferStroops": 1894921070, "distinctCounterparties30d": 2, "failedPaymentCount90d": 20, "disputeRatio90d": 0.772, "crossBorderTransferRatio30d": 0.708, "medianSettlementLatencySeconds": 2.67} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0190", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 1970, "transactionCount30d": 163, "averageBalanceStroops": 605779640, "largestTransferStroops": 426055888, "distinctCounterparties30d": 26, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.089, "medianSettlementLatencySeconds": 7.98} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0191", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1521, "transactionCount30d": 145, "averageBalanceStroops": 959759428, "largestTransferStroops": 82868638, "distinctCounterparties30d": 14, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.035, "medianSettlementLatencySeconds": 6.57} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0192", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 56, "transactionCount30d": 11, "averageBalanceStroops": 44817969, "largestTransferStroops": 56630633, "distinctCounterparties30d": 1, "failedPaymentCount90d": 20, "disputeRatio90d": 0.186, "crossBorderTransferRatio30d": 0.457, "medianSettlementLatencySeconds": 26.28} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0193", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1553, "transactionCount30d": 199, "averageBalanceStroops": 2838900744, "largestTransferStroops": 436112500, "distinctCounterparties30d": 33, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.045, "medianSettlementLatencySeconds": 7.93} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0194", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 584, "transactionCount30d": 146, "averageBalanceStroops": 212889043, "largestTransferStroops": 307437995, "distinctCounterparties30d": 46, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.093, "medianSettlementLatencySeconds": 7.92} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0195", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1353, "transactionCount30d": 42, "averageBalanceStroops": 382385977, "largestTransferStroops": 223760508, "distinctCounterparties30d": 40, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.082, "medianSettlementLatencySeconds": 5.05} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0196", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 0, "accountAgeDays": 3, "transactionCount30d": 212, "averageBalanceStroops": 1244282, "largestTransferStroops": 1466312868, "distinctCounterparties30d": 2, "failedPaymentCount90d": 48, "disputeRatio90d": 0.462, "crossBorderTransferRatio30d": 0.739, "medianSettlementLatencySeconds": 8.46} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0197", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 2, "accountAgeDays": 1672, "transactionCount30d": 53, "averageBalanceStroops": 4632963672, "largestTransferStroops": 76658495, "distinctCounterparties30d": 43, "failedPaymentCount90d": 0, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.061, "medianSettlementLatencySeconds": 3.81} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0198", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 808, "transactionCount30d": 152, "averageBalanceStroops": 3943677566, "largestTransferStroops": 464157202, "distinctCounterparties30d": 15, "failedPaymentCount90d": 1, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.039, "medianSettlementLatencySeconds": 7.78} +{"schemaVersion": "1.0.0", "subjectId": "account-sample-0199", "subjectKind": "account", "observedAt": "2026-01-15T00:00:00.000Z", "kycTier": 3, "accountAgeDays": 356, "transactionCount30d": 180, "averageBalanceStroops": 2391270703, "largestTransferStroops": 279816903, "distinctCounterparties30d": 20, "failedPaymentCount90d": 2, "disputeRatio90d": 0.0, "crossBorderTransferRatio30d": 0.188, "medianSettlementLatencySeconds": 3.77} diff --git a/ml/emitter.py b/ml/emitter.py new file mode 100644 index 0000000..39fdea6 --- /dev/null +++ b/ml/emitter.py @@ -0,0 +1,78 @@ +"""ModelResult emitter adhering strictly to the chenai-mlflow data contract. + +Wraps model inferences into verified ModelResult payloads and supports +attestation metadata generation matching Soroban model-attestation requirements. +""" + +from __future__ import annotations + +import datetime +from typing import Any, Dict, List, Optional + +from contract import ( + MODEL_RESULT_SCHEMA_VERSION, + impute_feature_vector, + validate_model_result, +) +from models import vector_to_features + + +def _get_utc_timestamp() -> str: + now = datetime.datetime.now(datetime.timezone.utc) + return now.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" + + +class ModelResultEmitter: + """Emits contract-compliant ModelResult dictionaries from model inferences.""" + + def __init__(self, artifact_hashes: Optional[Dict[str, str]] = None) -> None: + """Initialize the emitter with optional model artifact digests.""" + self.artifact_hashes = artifact_hashes or {} + + def score_and_emit( + self, + model: Any, + raw_or_imputed_vector: Dict[str, Any], + task: str, + scored_at: Optional[str] = None, + ) -> Dict[str, Any]: + """Execute inference on a feature vector and emit a validated ModelResult.""" + if "imputedFields" in raw_or_imputed_vector: + imputed_vector = raw_or_imputed_vector + imputed_fields = list(raw_or_imputed_vector["imputedFields"]) + else: + imputed_vector, imputed_fields = impute_feature_vector(raw_or_imputed_vector) + + feature_row = vector_to_features(imputed_vector) + score, label, confidence = model.predict_risk(feature_row) + + effective_scored_at = scored_at or _get_utc_timestamp() + + result: Dict[str, Any] = { + "schemaVersion": MODEL_RESULT_SCHEMA_VERSION, + "modelId": getattr(model, "MODEL_ID", f"{task}-model"), + "modelVersion": getattr(model, "MODEL_VERSION", "1.0.0"), + "featureVectorVersion": imputed_vector.get("schemaVersion", "1.0.0"), + "task": task, + "subjectId": imputed_vector["subjectId"], + "scoredAt": effective_scored_at, + "score": score, + "label": label, + "confidence": confidence, + "imputedFields": imputed_fields, + } + + return validate_model_result(result) + + def get_attestation_payload( + self, + result: Dict[str, Any], + artifact_hash: Optional[str] = None, + ) -> Dict[str, Any]: + """Create an attestation payload binding ModelResult to a 32-byte artifact hash.""" + target_hash = artifact_hash or self.artifact_hashes.get(result["modelId"], "") + return { + "modelResult": result, + "artifactHash": target_hash, + "attestationTarget": "contracts/model-attestation", + } diff --git a/ml/features.py b/ml/features.py new file mode 100644 index 0000000..b6d961c --- /dev/null +++ b/ml/features.py @@ -0,0 +1,192 @@ +"""Feature builder for translating Horizon data into FeatureVector payloads. + +Extracts the nine numeric features defined in the contract from account state, +payments, operations, and transaction logs. +""" + +from __future__ import annotations + +import datetime +import statistics +from typing import Any, Dict, List, Optional + +from contract import ( + FEATURE_VECTOR_SCHEMA_VERSION, + STROOPS_PER_XLM, + validate_feature_vector, +) + + +def _parse_iso_utc(ts_str: str) -> datetime.datetime: + clean = ts_str.rstrip("Z") + dt = datetime.datetime.fromisoformat(clean) + if dt.tzinfo is None: + dt = dt.replace(tzinfo=datetime.timezone.utc) + return dt + + +def _to_iso_utc_string(dt: datetime.datetime) -> str: + utc_dt = dt.astimezone(datetime.timezone.utc) + return utc_dt.strftime("%Y-%m-%dT%H:%M:%S.000Z") + + +class FeatureBuilder: + """Transforms raw Stellar Horizon payloads into contract-compliant FeatureVectors.""" + + def __init__(self, default_kyc_tier: int = 0) -> None: + """Initialize the feature builder with a fallback KYC tier.""" + self.default_kyc_tier = default_kyc_tier + + def build_from_horizon( + self, + account_id: str, + account_data: Optional[Dict[str, Any]] = None, + operations: Optional[List[Dict[str, Any]]] = None, + payments: Optional[List[Dict[str, Any]]] = None, + transactions: Optional[List[Dict[str, Any]]] = None, + kyc_tier: Optional[int] = None, + observed_at: Optional[str] = None, + ) -> Dict[str, Any]: + """Construct a validated FeatureVector from raw Horizon API data.""" + if observed_at is None: + observed_at_dt = datetime.datetime.now(datetime.timezone.utc) + observed_at_str = _to_iso_utc_string(observed_at_dt) + else: + observed_at_dt = _parse_iso_utc(observed_at) + observed_at_str = observed_at + + effective_kyc_tier = self.default_kyc_tier if kyc_tier is None else kyc_tier + + account_age_days: Optional[int] = None + if operations: + earliest_op_time: Optional[datetime.datetime] = None + for op in operations: + created_str = op.get("created_at") + if created_str: + op_dt = _parse_iso_utc(created_str) + if earliest_op_time is None or op_dt < earliest_op_time: + earliest_op_time = op_dt + if earliest_op_time is not None and observed_at_dt >= earliest_op_time: + age_delta = observed_at_dt - earliest_op_time + account_age_days = max(0, min(36500, int(age_delta.total_seconds() // 86400))) + + cutoff_30d = observed_at_dt - datetime.timedelta(days=30) + cutoff_90d = observed_at_dt - datetime.timedelta(days=90) + + tx_count_30d: Optional[int] = None + largest_transfer_stroops: Optional[int] = None + distinct_counterparties: Optional[int] = None + cross_border_ratio: Optional[float] = None + + if payments is not None: + payments_30d = [ + p for p in payments + if p.get("created_at") and _parse_iso_utc(p["created_at"]) >= cutoff_30d + ] + tx_count_30d = min(1_000_000_000, len(payments_30d)) + + counterparties = set() + max_transfer = 0 + cross_border_volume = 0.0 + total_volume = 0.0 + + for p in payments_30d: + from_acc = p.get("from") or p.get("source_account") + to_acc = p.get("to") or p.get("account") + if from_acc == account_id and to_acc: + counterparties.add(to_acc) + elif to_acc == account_id and from_acc: + counterparties.add(from_acc) + + amount_str = p.get("amount") + if amount_str: + try: + amt_xlm = float(amount_str) + amt_stroops = int(amt_xlm * STROOPS_PER_XLM) + total_volume += amt_xlm + if from_acc == account_id: + if amt_stroops > max_transfer: + max_transfer = amt_stroops + if p.get("asset_type") != "native": + cross_border_volume += amt_xlm + except (ValueError, TypeError): + pass + + distinct_counterparties = min(1_000_000_000, len(counterparties)) + largest_transfer_stroops = min(9007199254740991, max_transfer) + cross_border_ratio = ( + round(cross_border_volume / total_volume, 4) + if total_volume > 0.0 + else 0.0 + ) + + avg_balance_stroops: Optional[int] = None + if account_data and "balances" in account_data: + native_balance_xlm = 0.0 + for b in account_data["balances"]: + if b.get("asset_type") == "native": + try: + native_balance_xlm = float(b.get("balance", "0")) + except (ValueError, TypeError): + pass + break + avg_balance_stroops = min( + 9007199254740991, + max(0, int(native_balance_xlm * STROOPS_PER_XLM)) + ) + + failed_payments_90d: Optional[int] = None + dispute_ratio_90d: Optional[float] = None + settlement_latencies: List[float] = [] + + if transactions is not None: + txs_90d = [ + tx for tx in transactions + if tx.get("created_at") and _parse_iso_utc(tx["created_at"]) >= cutoff_90d + ] + failed_count = sum(1 for tx in txs_90d if tx.get("successful") is False) + failed_payments_90d = min(1_000_000_000, failed_count) + + disputed_count = sum( + 1 for tx in txs_90d + if tx.get("memo", "").lower().startswith("dispute") + or tx.get("disputed") is True + ) + dispute_ratio_90d = ( + round(disputed_count / len(txs_90d), 4) + if len(txs_90d) > 0 + else 0.0 + ) + + for tx in txs_90d: + latency = tx.get("latency_seconds") + if latency is not None: + try: + lat_float = float(latency) + if 0.0 <= lat_float <= 2592000.0: + settlement_latencies.append(lat_float) + except (ValueError, TypeError): + pass + + median_latency: Optional[float] = None + if settlement_latencies: + median_latency = round(statistics.median(settlement_latencies), 3) + + vector: Dict[str, Any] = { + "schemaVersion": FEATURE_VECTOR_SCHEMA_VERSION, + "subjectId": account_id, + "subjectKind": "account", + "observedAt": observed_at_str, + "kycTier": effective_kyc_tier, + "accountAgeDays": account_age_days, + "transactionCount30d": tx_count_30d, + "averageBalanceStroops": avg_balance_stroops, + "largestTransferStroops": largest_transfer_stroops, + "distinctCounterparties30d": distinct_counterparties, + "failedPaymentCount90d": failed_payments_90d, + "disputeRatio90d": dispute_ratio_90d, + "crossBorderTransferRatio30d": cross_border_ratio, + "medianSettlementLatencySeconds": median_latency, + } + + return validate_feature_vector(vector) diff --git a/ml/ingest.py b/ml/ingest.py new file mode 100644 index 0000000..fd581e3 --- /dev/null +++ b/ml/ingest.py @@ -0,0 +1,101 @@ +"""Stellar Horizon ingestion client. + +Fetches account balances, operation logs, payments, and transaction history +from a Stellar Horizon server to construct raw signals for the FeatureVector builder. +""" + +from __future__ import annotations + +import datetime +from typing import Any, Dict, List, Optional +import requests + + +class HorizonError(RuntimeError): + """Raised when an interaction with the Stellar Horizon API fails.""" + + +class StellarHorizonClient: + """Client for pulling account history, operations, and balances from Horizon.""" + + def __init__( + self, + server_url: str = "https://horizon-testnet.stellar.org", + timeout: float = 10.0, + session: Optional[requests.Session] = None, + ) -> None: + """Initialize the client with server URL and timeout settings.""" + self.server_url = server_url.rstrip("/") + self.timeout = timeout + self.session = session or requests.Session() + + def get_account(self, account_id: str) -> Dict[str, Any]: + """Fetch current account record including native and asset balances.""" + url = f"{self.server_url}/accounts/{account_id}" + try: + resp = self.session.get(url, timeout=self.timeout) + if resp.status_code == 404: + raise HorizonError(f"Account {account_id} not found on Horizon") + resp.raise_for_status() + return resp.json() + except requests.RequestException as exc: + raise HorizonError(f"Horizon account query failed for {account_id}: {exc}") from exc + + def get_operations( + self, account_id: str, limit: int = 200, order: str = "desc" + ) -> List[Dict[str, Any]]: + """Fetch account operation history.""" + url = f"{self.server_url}/accounts/{account_id}/operations" + params = {"limit": limit, "order": order} + try: + resp = self.session.get(url, params=params, timeout=self.timeout) + resp.raise_for_status() + records = resp.json().get("_embedded", {}).get("records", []) + return records + except requests.RequestException as exc: + raise HorizonError(f"Horizon operations query failed for {account_id}: {exc}") from exc + + def get_payments( + self, account_id: str, limit: int = 200, order: str = "desc" + ) -> List[Dict[str, Any]]: + """Fetch account payment records.""" + url = f"{self.server_url}/accounts/{account_id}/payments" + params = {"limit": limit, "order": order} + try: + resp = self.session.get(url, params=params, timeout=self.timeout) + resp.raise_for_status() + records = resp.json().get("_embedded", {}).get("records", []) + return records + except requests.RequestException as exc: + raise HorizonError(f"Horizon payments query failed for {account_id}: {exc}") from exc + + def get_transactions( + self, account_id: str, limit: int = 200, order: str = "desc" + ) -> List[Dict[str, Any]]: + """Fetch account transactions including failed or reverted transactions.""" + url = f"{self.server_url}/accounts/{account_id}/transactions" + params = {"limit": limit, "order": order} + try: + resp = self.session.get(url, params=params, timeout=self.timeout) + resp.raise_for_status() + records = resp.json().get("_embedded", {}).get("records", []) + return records + except requests.RequestException as exc: + raise HorizonError(f"Horizon transactions query failed for {account_id}: {exc}") from exc + + def collect_account_data(self, account_id: str) -> Dict[str, Any]: + """Collect account snapshot, operations, payments, and transactions.""" + now = datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z") + account = self.get_account(account_id) + operations = self.get_operations(account_id, limit=200, order="asc") + payments = self.get_payments(account_id, limit=200, order="desc") + transactions = self.get_transactions(account_id, limit=200, order="desc") + + return { + "account_id": account_id, + "account": account, + "operations": operations, + "payments": payments, + "transactions": transactions, + "fetched_at": now, + } diff --git a/ml/models.py b/ml/models.py new file mode 100644 index 0000000..e74dbc2 --- /dev/null +++ b/ml/models.py @@ -0,0 +1,158 @@ +"""Predictive models for credit scoring and fraud anomaly detection. + +Implements scikit-learn baseline models operating on imputed FeatureVectors, +exporting model weights and calculating SHA-256 artifact digests compatible +with the Soroban model-attestation contract. +""" + +from __future__ import annotations + +import hashlib +import math +from pathlib import Path +from typing import Any, Dict, List, Tuple +import joblib +import numpy as np +from sklearn.ensemble import GradientBoostingClassifier, IsolationForest + +FEATURE_COLUMNS: List[str] = [ + "accountAgeDays", + "transactionCount30d", + "averageBalanceStroops", + "largestTransferStroops", + "distinctCounterparties30d", + "failedPaymentCount90d", + "disputeRatio90d", + "crossBorderTransferRatio30d", + "medianSettlementLatencySeconds", + "kycTier", +] + + +def vector_to_features(vector: Dict[str, Any]) -> List[float]: + """Convert an imputed FeatureVector into an ordered numeric feature array.""" + row: List[float] = [] + for col in FEATURE_COLUMNS: + val = vector.get(col, 0) + row.append(float(val) if val is not None else 0.0) + return row + + +def compute_artifact_hash(filepath: Path) -> str: + """Compute 32-byte SHA-256 hexadecimal digest of a model artifact.""" + hasher = hashlib.sha256() + with open(filepath, "rb") as f: + while chunk := f.read(65536): + hasher.update(chunk) + return hasher.hexdigest() + + +class CreditScoreModel: + """Gradient boosting model for credit default risk estimation.""" + + MODEL_ID = "credit-score-gbm" + MODEL_VERSION = "1.0.0" + + def __init__(self, random_state: int = 42) -> None: + """Initialize the credit score classifier with a deterministic seed.""" + self.random_state = random_state + self.clf = GradientBoostingClassifier( + n_estimators=60, + max_depth=3, + learning_rate=0.1, + random_state=random_state, + ) + self.is_fitted = False + + def fit(self, x_data: np.ndarray, y_labels: np.ndarray) -> "CreditScoreModel": + """Fit the gradient boosting estimator on training data.""" + self.clf.fit(x_data, y_labels) + self.is_fitted = True + return self + + def predict_risk(self, feature_row: List[float]) -> Tuple[float, str, float]: + """Predict continuous credit risk score, discrete risk label, and confidence.""" + if not self.is_fitted: + raise RuntimeError("CreditScoreModel is not fitted") + + x_arr = np.array([feature_row], dtype=float) + probs = self.clf.predict_proba(x_arr)[0] + risk_score = float(np.clip(probs[1], 0.0, 1.0)) + risk_score = round(risk_score, 4) + + if risk_score < 0.33: + label = "low" + elif risk_score < 0.66: + label = "medium" + else: + label = "high" + + confidence = round(float(np.max(probs)), 4) + return risk_score, label, confidence + + def save(self, destination: Path) -> str: + """Persist model to disk and return its SHA-256 artifact hash.""" + destination.parent.mkdir(parents=True, exist_ok=True) + joblib.dump(self.clf, destination) + return compute_artifact_hash(destination) + + def load(self, source: Path) -> "CreditScoreModel": + """Load model from disk.""" + self.clf = joblib.load(source) + self.is_fitted = True + return self + + +class FraudDetectModel: + """Isolation Forest anomaly detection model for payment and account fraud.""" + + MODEL_ID = "fraud-detect-iforest" + MODEL_VERSION = "1.0.0" + + def __init__(self, random_state: int = 42) -> None: + """Initialize the isolation forest detector with a deterministic seed.""" + self.random_state = random_state + self.detector = IsolationForest( + n_estimators=60, + contamination=0.1, + random_state=random_state, + ) + self.is_fitted = False + + def fit(self, x_data: np.ndarray) -> "FraudDetectModel": + """Fit the isolation forest on training data.""" + self.detector.fit(x_data) + self.is_fitted = True + return self + + def predict_risk(self, feature_row: List[float]) -> Tuple[float, str, float]: + """Predict continuous anomaly risk score, risk label, and confidence.""" + if not self.is_fitted: + raise RuntimeError("FraudDetectModel is not fitted") + + x_arr = np.array([feature_row], dtype=float) + decision = float(self.detector.decision_function(x_arr)[0]) + risk_score = 1.0 / (1.0 + math.exp(decision * 6.0)) + risk_score = round(float(np.clip(risk_score, 0.0, 1.0)), 4) + + if risk_score < 0.33: + label = "low" + elif risk_score < 0.66: + label = "medium" + else: + label = "high" + + confidence = round(float(np.clip(abs(risk_score - 0.5) * 2.0, 0.0, 1.0)), 4) + return risk_score, label, confidence + + def save(self, destination: Path) -> str: + """Persist model to disk and return its SHA-256 artifact hash.""" + destination.parent.mkdir(parents=True, exist_ok=True) + joblib.dump(self.detector, destination) + return compute_artifact_hash(destination) + + def load(self, source: Path) -> "FraudDetectModel": + """Load model from disk.""" + self.detector = joblib.load(source) + self.is_fitted = True + return self diff --git a/ml/pipeline.py b/ml/pipeline.py new file mode 100644 index 0000000..a777364 --- /dev/null +++ b/ml/pipeline.py @@ -0,0 +1,223 @@ +"""Pipeline runner and CLI entrypoint for scoring accounts. + +Supports single-subject live scoring via Horizon and batch scoring from files, +routing validated ModelResults to file-drop, SQLite, or stdout sinks. +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from contract import impute_feature_vector, validate_feature_vector +from emitter import ModelResultEmitter +from features import FeatureBuilder +from ingest import HorizonError, StellarHorizonClient +from models import CreditScoreModel, FraudDetectModel +from sinks import BaseSink, FileDropSink, SqliteSink, StdoutSink + + +class PipelineRunner: + """Orchestrates ingestion, feature building, model scoring, and result sinking.""" + + def __init__( + self, + artifacts_dir: Optional[Path] = None, + sink: Optional[BaseSink] = None, + horizon_client: Optional[StellarHorizonClient] = None, + ) -> None: + """Initialize pipeline models, manifest, sink, and Horizon ingest client.""" + base_dir = artifacts_dir or (Path(__file__).resolve().parent / "artifacts") + self.artifacts_dir = base_dir + self.sink = sink or StdoutSink() + self.horizon_client = horizon_client or StellarHorizonClient() + self.feature_builder = FeatureBuilder() + + self.credit_model = CreditScoreModel() + credit_path = base_dir / "credit_score_gbm.joblib" + if credit_path.exists(): + self.credit_model.load(credit_path) + + self.fraud_model = FraudDetectModel() + fraud_path = base_dir / "fraud_detect_iforest.joblib" + if fraud_path.exists(): + self.fraud_model.load(fraud_path) + + manifest_path = base_dir / "manifest.json" + artifact_hashes: Dict[str, str] = {} + if manifest_path.exists(): + with open(manifest_path, "r", encoding="utf-8") as f: + manifest_data = json.load(f) + for mid, mdata in manifest_data.get("models", {}).items(): + artifact_hashes[mid] = mdata.get("artifact_hash_sha256", "") + + self.emitter = ModelResultEmitter(artifact_hashes=artifact_hashes) + + def score_vector( + self, + vector: Dict[str, Any], + task: str = "all", + ) -> List[Dict[str, Any]]: + """Score a single FeatureVector across the specified task(s).""" + imputed_vector, _ = impute_feature_vector(vector) + results: List[Dict[str, Any]] = [] + + if task in ("credit-score", "all"): + res = self.emitter.score_and_emit( + self.credit_model, imputed_vector, "credit-score" + ) + results.append(res) + + if task in ("fraud-detect", "all"): + res = self.emitter.score_and_emit( + self.fraud_model, imputed_vector, "fraud-detect" + ) + results.append(res) + + self.sink.write(results) + return results + + def score_account( + self, + account_id: str, + task: str = "all", + kyc_tier: int = 0, + ) -> List[Dict[str, Any]]: + """Fetch account history from Horizon, build FeatureVector, and score.""" + try: + account_data = self.horizon_client.get_account(account_id) + operations = self.horizon_client.get_operations(account_id, limit=200) + payments = self.horizon_client.get_payments(account_id, limit=200) + transactions = self.horizon_client.get_transactions(account_id, limit=200) + except HorizonError: + account_data = None + operations = None + payments = None + transactions = None + + vector = self.feature_builder.build_from_horizon( + account_id=account_id, + account_data=account_data, + operations=operations, + payments=payments, + transactions=transactions, + kyc_tier=kyc_tier, + ) + + return self.score_vector(vector, task=task) + + def score_batch_file( + self, + file_path: Path, + task: str = "all", + ) -> List[Dict[str, Any]]: + """Read and score a batch file containing FeatureVectors (JSON or JSONL).""" + all_results: List[Dict[str, Any]] = [] + + with open(file_path, "r", encoding="utf-8") as f: + content = f.read().strip() + + if file_path.suffix == ".jsonl" or ("\n" in content and not content.startswith("[")): + lines = [line.strip() for line in content.splitlines() if line.strip()] + for line in lines: + raw_item = json.loads(line) + valid_vec = validate_feature_vector(raw_item) + results = self.score_vector(valid_vec, task=task) + all_results.extend(results) + else: + items = json.loads(content) + if isinstance(items, dict): + items = [items] + for item in items: + valid_vec = validate_feature_vector(item) + results = self.score_vector(valid_vec, task=task) + all_results.extend(results) + + return all_results + + +def create_sink(sink_type: str, output_path: Optional[str]) -> BaseSink: + """Instantiate the requested output sink based on CLI configuration.""" + if sink_type == "stdout": + return StdoutSink() + if sink_type == "sqlite": + path = output_path or "ml/output/results.db" + return SqliteSink(path) + path = output_path or "ml/output/results.jsonl" + return FileDropSink(path) + + +def main() -> None: + """CLI entrypoint for executing ML scoring pipelines.""" + parser = argparse.ArgumentParser(description="Chenaikit ML Scoring Pipeline") + parser.add_argument( + "--mode", + choices=["single", "batch"], + default="single", + help="Execution mode (single account or batch file)", + ) + parser.add_argument( + "--subject-id", + type=str, + help="Account public key or subject ID for single scoring", + ) + parser.add_argument( + "--input-file", + type=Path, + help="Input JSON or JSONL file path for batch mode or single vector file", + ) + parser.add_argument( + "--task", + choices=["credit-score", "fraud-detect", "all"], + default="all", + help="Scoring task to execute", + ) + parser.add_argument( + "--sink", + choices=["file", "sqlite", "stdout"], + default="stdout", + help="Output destination sink", + ) + parser.add_argument( + "--output", + type=str, + help="Output file or SQLite path (defaults to ml/output/results.jsonl or results.db)", + ) + parser.add_argument( + "--horizon-url", + type=str, + default="https://horizon-testnet.stellar.org", + help="Stellar Horizon endpoint URL", + ) + parser.add_argument( + "--kyc-tier", + type=int, + choices=[0, 1, 2, 3], + default=0, + help="KYC tier for subject", + ) + + args = parser.parse_args() + sink = create_sink(args.sink, args.output) + horizon_client = StellarHorizonClient(server_url=args.horizon_url) + runner = PipelineRunner(sink=sink, horizon_client=horizon_client) + + if args.mode == "single": + if args.input_file: + with open(args.input_file, "r", encoding="utf-8") as f: + data = json.load(f) + runner.score_vector(data, task=args.task) + elif args.subject_id: + runner.score_account(args.subject_id, task=args.task, kyc_tier=args.kyc_tier) + else: + parser.error("Single mode requires either --subject-id or --input-file") + elif args.mode == "batch": + if not args.input_file: + parser.error("Batch mode requires --input-file") + runner.score_batch_file(args.input_file, task=args.task) + + +if __name__ == "__main__": + main() diff --git a/ml/pyproject.toml b/ml/pyproject.toml new file mode 100644 index 0000000..83c638f --- /dev/null +++ b/ml/pyproject.toml @@ -0,0 +1,28 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "chenai-ml" +version = "1.0.0" +description = "Machine learning pipeline for credit scoring and fraud detection in chenaikit" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "numpy>=1.26.0", + "scikit-learn>=1.4.0", + "joblib>=1.3.0", + "requests>=2.31.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0.0", + "pytest-asyncio>=0.23.0", +] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] diff --git a/ml/requirements.txt b/ml/requirements.txt new file mode 100644 index 0000000..3c3334a --- /dev/null +++ b/ml/requirements.txt @@ -0,0 +1,5 @@ +numpy>=1.26.0 +scikit-learn>=1.4.0 +joblib>=1.3.0 +requests>=2.31.0 +pytest>=8.0.0 diff --git a/ml/sinks.py b/ml/sinks.py new file mode 100644 index 0000000..f685d04 --- /dev/null +++ b/ml/sinks.py @@ -0,0 +1,151 @@ +"""Output sinks for persisted ModelResult records. + +Provides destination sinks for single-subject and batch pipeline execution, +including atomic file drops, direct SQLite persistence, and stdout streaming. +""" + +from __future__ import annotations + +import abc +import json +import os +import sqlite3 +import sys +import tempfile +from pathlib import Path +from typing import Any, Dict, List, Union + + +class BaseSink(abc.ABC): + """Abstract base class for ModelResult output sinks.""" + + @abc.abstractmethod + def write(self, results: Union[Dict[str, Any], List[Dict[str, Any]]]) -> None: + """Persist a single ModelResult or a list of ModelResults to the sink.""" + + +class FileDropSink(BaseSink): + """File drop sink writing newline-delimited JSON or atomic single-record files.""" + + def __init__(self, output_path: Union[str, Path]) -> None: + """Initialize sink with target file or directory path.""" + self.output_path = Path(output_path) + + def write(self, results: Union[Dict[str, Any], List[Dict[str, Any]]]) -> None: + """Write model results atomically to the destination file or directory.""" + records = [results] if isinstance(results, dict) else results + if not records: + return + + if self.output_path.suffix == ".jsonl": + self.output_path.parent.mkdir(parents=True, exist_ok=True) + with open(self.output_path, "a", encoding="utf-8") as f: + for r in records: + f.write(json.dumps(r) + "\n") + elif self.output_path.is_dir() or self.output_path.suffix == "": + self.output_path.mkdir(parents=True, exist_ok=True) + for r in records: + fname = f"{r['subjectId']}_{r['task']}.json" + target = self.output_path / fname + temp_fd, temp_path = tempfile.mkstemp( + dir=self.output_path, prefix="tmp_drop_", suffix=".json" + ) + with os.fdopen(temp_fd, "w", encoding="utf-8") as f: + json.dump(r, f, indent=2) + os.replace(temp_path, target) + else: + self.output_path.parent.mkdir(parents=True, exist_ok=True) + temp_dir = self.output_path.parent + temp_fd, temp_path = tempfile.mkstemp( + dir=temp_dir, prefix="tmp_sink_", suffix=self.output_path.suffix + ) + with os.fdopen(temp_fd, "w", encoding="utf-8") as f: + json.dump(records if len(records) > 1 else records[0], f, indent=2) + os.replace(temp_path, self.output_path) + + +class SqliteSink(BaseSink): + """Direct SQLite database sink creating and updating model_results tables.""" + + def __init__(self, db_path: Union[str, Path]) -> None: + """Initialize SQLite sink and ensure target table schema exists.""" + self.db_path = Path(db_path) + self.db_path.parent.mkdir(parents=True, exist_ok=True) + self._ensure_schema() + + def _ensure_schema(self) -> None: + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute( + """ + CREATE TABLE IF NOT EXISTS model_results ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + subject_id TEXT NOT NULL, + task TEXT NOT NULL, + model_id TEXT NOT NULL, + model_version TEXT NOT NULL, + score REAL NOT NULL, + label TEXT NOT NULL, + confidence REAL NOT NULL, + scored_at TEXT NOT NULL, + imputed_fields TEXT NOT NULL, + payload TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + cursor.execute( + "CREATE INDEX IF NOT EXISTS idx_subject_task ON model_results(subject_id, task)" + ) + conn.commit() + + def write(self, results: Union[Dict[str, Any], List[Dict[str, Any]]]) -> None: + """Insert records into the SQLite model_results table.""" + records = [results] if isinstance(results, dict) else results + if not records: + return + + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + for r in records: + cursor.execute( + """ + INSERT INTO model_results ( + subject_id, task, model_id, model_version, + score, label, confidence, scored_at, + imputed_fields, payload + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + r["subjectId"], + r["task"], + r["modelId"], + r["modelVersion"], + float(r["score"]), + r["label"], + float(r["confidence"]), + r["scoredAt"], + json.dumps(r["imputedFields"]), + json.dumps(r), + ), + ) + conn.commit() + + +class StdoutSink(BaseSink): + """Standard output sink emitting newline-delimited JSON strings.""" + + def write(self, results: Union[Dict[str, Any], List[Dict[str, Any]]]) -> None: + """Stream model result JSON records to stdout.""" + if getattr(sys.stdout, "closed", False): + return + records = [results] if isinstance(results, dict) else results + try: + for r in records: + sys.stdout.write(json.dumps(r) + "\n") + sys.stdout.flush() + except (BrokenPipeError, OSError, ValueError): + try: + sys.stdout.close() + except Exception: + pass diff --git a/ml/tests/__init__.py b/ml/tests/__init__.py index e69de29..45438d5 100644 --- a/ml/tests/__init__.py +++ b/ml/tests/__init__.py @@ -0,0 +1,6 @@ +import sys +from pathlib import Path + +ml_root = str(Path(__file__).resolve().parent.parent) +if ml_root not in sys.path: + sys.path.insert(0, ml_root) diff --git a/ml/tests/node_validator_harness.js b/ml/tests/node_validator_harness.js new file mode 100644 index 0000000..14ee046 --- /dev/null +++ b/ml/tests/node_validator_harness.js @@ -0,0 +1,48 @@ +const { + validateFeatureVector, + imputeFeatureVector, + validateModelResult, + EXAMPLE_FEATURE_VECTOR, + EXAMPLE_FEATURE_VECTOR_WITH_GAPS, + EXAMPLE_MODEL_RESULT, + EXAMPLE_MODEL_RESULT_WITH_IMPUTATION, +} = require("../../packages/chenai-mlflow/dist/index.js"); + +const command = process.argv[2]; +const payloadStr = process.argv[3]; + +try { + if (command === "validate-feature-vector") { + const payload = JSON.parse(payloadStr); + const validated = validateFeatureVector(payload); + process.stdout.write(JSON.stringify({ success: true, result: validated })); + } else if (command === "impute-feature-vector") { + const payload = JSON.parse(payloadStr); + const imputed = imputeFeatureVector(payload); + process.stdout.write(JSON.stringify({ success: true, result: imputed })); + } else if (command === "validate-model-result") { + const payload = JSON.parse(payloadStr); + const validated = validateModelResult(payload); + process.stdout.write(JSON.stringify({ success: true, result: validated })); + } else if (command === "export-examples") { + process.stdout.write( + JSON.stringify({ + EXAMPLE_FEATURE_VECTOR, + EXAMPLE_FEATURE_VECTOR_WITH_GAPS, + EXAMPLE_MODEL_RESULT, + EXAMPLE_MODEL_RESULT_WITH_IMPUTATION, + }), + ); + } else { + process.stderr.write(`Unknown command: ${command}\n`); + process.exit(1); + } +} catch (err) { + process.stdout.write( + JSON.stringify({ + success: false, + error: err.message, + issues: err.issues || [], + }), + ); +} diff --git a/ml/tests/test_contract.py b/ml/tests/test_contract.py new file mode 100644 index 0000000..21d679f --- /dev/null +++ b/ml/tests/test_contract.py @@ -0,0 +1,155 @@ +"""Unit tests for contract loading, validation, and imputation.""" + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +import unittest +from contract import ( + FEATURE_VECTOR_SCHEMA_VERSION, + MODEL_RESULT_SCHEMA_VERSION, + SchemaValidationError, + impute_feature_vector, + load_numeric_feature_specs, + validate_feature_vector, + validate_model_result, +) + + +class TestContractValidation(unittest.TestCase): + """Test suite for contract schema rules and missing-value imputation.""" + + def setUp(self) -> None: + """Create valid baseline payloads for tests.""" + self.valid_feature_vector = { + "schemaVersion": FEATURE_VECTOR_SCHEMA_VERSION, + "subjectId": "account-valid-001", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 2, + "accountAgeDays": 100, + "transactionCount30d": 15, + "averageBalanceStroops": 500000000, + "largestTransferStroops": 100000000, + "distinctCounterparties30d": 5, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.1, + "medianSettlementLatencySeconds": 4.5, + } + + self.valid_model_result = { + "schemaVersion": MODEL_RESULT_SCHEMA_VERSION, + "modelId": "credit-score-gbm", + "modelVersion": "1.0.0", + "featureVectorVersion": "1.0.0", + "task": "credit-score", + "subjectId": "account-valid-001", + "scoredAt": "2026-01-15T00:00:01.000Z", + "score": 0.25, + "label": "low", + "confidence": 0.85, + "imputedFields": [], + } + + def test_load_numeric_feature_specs(self) -> None: + """Ensure specs are extracted accurately from TypeScript source.""" + specs = load_numeric_feature_specs() + self.assertEqual(len(specs), 9) + names = [s.name for s in specs] + self.assertIn("accountAgeDays", names) + self.assertIn("medianSettlementLatencySeconds", names) + + latency_spec = next(s for s in specs if s.name == "medianSettlementLatencySeconds") + self.assertEqual(latency_spec.missing_default, 86400.0) + + def test_validate_valid_feature_vector(self) -> None: + """Validate a well-formed feature vector successfully.""" + result = validate_feature_vector(self.valid_feature_vector) + self.assertEqual(result["subjectId"], "account-valid-001") + + def test_reject_unknown_field(self) -> None: + """Ensure unapproved fields are rejected.""" + payload = dict(self.valid_feature_vector) + payload["unauthorizedField"] = "bad" + with self.assertRaises(SchemaValidationError) as ctx: + validate_feature_vector(payload) + self.assertIn("unknown field", str(ctx.exception)) + + def test_reject_unsupported_schema_version(self) -> None: + """Ensure unsupported schema version is rejected.""" + payload = dict(self.valid_feature_vector) + payload["schemaVersion"] = "2.0.0" + with self.assertRaises(SchemaValidationError) as ctx: + validate_feature_vector(payload) + self.assertIn("schemaVersion 2.0.0 is not supported", str(ctx.exception)) + + def test_reject_personal_data_in_subject_id(self) -> None: + """Ensure subjectId containing PII symbols is rejected.""" + payload = dict(self.valid_feature_vector) + payload["subjectId"] = "ada.lovelace@example.com" + with self.assertRaises(SchemaValidationError) as ctx: + validate_feature_vector(payload) + self.assertIn("must not carry personal data", str(ctx.exception)) + + def test_reject_missing_numeric_field(self) -> None: + """Ensure omission of a numeric field is rejected (explicit null required).""" + payload = dict(self.valid_feature_vector) + del payload["averageBalanceStroops"] + with self.assertRaises(SchemaValidationError) as ctx: + validate_feature_vector(payload) + self.assertIn("averageBalanceStroops is required", str(ctx.exception)) + + def test_reject_out_of_bounds_numeric_field(self) -> None: + """Ensure values outside defined boundaries are rejected.""" + payload = dict(self.valid_feature_vector) + payload["disputeRatio90d"] = 1.5 + with self.assertRaises(SchemaValidationError) as ctx: + validate_feature_vector(payload) + self.assertIn("disputeRatio90d must be within", str(ctx.exception)) + + def test_reject_fractional_integer_field(self) -> None: + """Ensure integer fields reject float fractions.""" + payload = dict(self.valid_feature_vector) + payload["accountAgeDays"] = 12.5 + with self.assertRaises(SchemaValidationError) as ctx: + validate_feature_vector(payload) + self.assertIn("accountAgeDays must be an integer", str(ctx.exception)) + + def test_impute_feature_vector(self) -> None: + """Ensure null values impute to their documented defaults.""" + payload = dict(self.valid_feature_vector) + payload["accountAgeDays"] = None + payload["medianSettlementLatencySeconds"] = None + + imputed, fields = impute_feature_vector(payload) + self.assertEqual(imputed["accountAgeDays"], 0) + self.assertEqual(imputed["medianSettlementLatencySeconds"], 86400.0) + self.assertEqual(set(fields), {"accountAgeDays", "medianSettlementLatencySeconds"}) + self.assertEqual(imputed["imputedFields"], fields) + + def test_validate_valid_model_result(self) -> None: + """Validate a well-formed ModelResult successfully.""" + result = validate_model_result(self.valid_model_result) + self.assertEqual(result["label"], "low") + + def test_reject_invalid_model_result_label(self) -> None: + """Ensure non-standard risk labels are rejected.""" + payload = dict(self.valid_model_result) + payload["label"] = "critical" + with self.assertRaises(SchemaValidationError) as ctx: + validate_model_result(payload) + self.assertIn("label must be one of", str(ctx.exception)) + + def test_reject_invalid_model_result_score_range(self) -> None: + """Ensure scores outside [0, 1] are rejected.""" + payload = dict(self.valid_model_result) + payload["score"] = 1.2 + with self.assertRaises(SchemaValidationError) as ctx: + validate_model_result(payload) + self.assertIn("score must be within [0, 1]", str(ctx.exception)) + + +if __name__ == "__main__": + unittest.main() diff --git a/ml/tests/test_cross_language.py b/ml/tests/test_cross_language.py new file mode 100644 index 0000000..dae78cc --- /dev/null +++ b/ml/tests/test_cross_language.py @@ -0,0 +1,143 @@ +"""Cross-language contract verification between Python and TypeScript packages.""" + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +import json +import subprocess +import unittest + +from contract import ( + impute_feature_vector, + validate_feature_vector, + validate_model_result, +) +from emitter import ModelResultEmitter +from features import FeatureBuilder +from models import CreditScoreModel + + +class TestCrossLanguageContract(unittest.TestCase): + """Test suite verifying contract compliance across Python and TypeScript implementations.""" + + @classmethod + def setUpClass(cls) -> None: + """Locate harness and verify Node environment.""" + cls.harness_path = Path(__file__).resolve().parent / "node_validator_harness.js" + cls.assertTrue(cls.harness_path.exists(), "Node validator harness must exist") + + def _call_node_harness(self, command: str, payload: dict) -> dict: + cmd = ["node", str(self.harness_path), command, json.dumps(payload)] + proc = subprocess.run(cmd, capture_output=True, text=True, check=True) + return json.loads(proc.stdout) + + def test_python_generated_feature_vector_passes_typescript_validator(self) -> None: + """Ensure Python FeatureBuilder output is accepted by TypeScript validateFeatureVector.""" + builder = FeatureBuilder(default_kyc_tier=2) + vector = builder.build_from_horizon( + account_id="account-cross-01", + account_data={"balances": [{"asset_type": "native", "balance": "150.25"}]}, + operations=[{"created_at": "2025-01-01T00:00:00Z"}], + payments=[ + { + "created_at": "2026-01-10T00:00:00Z", + "from": "account-cross-01", + "to": "account-counterparty-02", + "amount": "25.0", + "asset_type": "native", + } + ], + transactions=[ + { + "created_at": "2026-01-10T00:00:00Z", + "successful": True, + "latency_seconds": 3.8, + } + ], + observed_at="2026-01-15T00:00:00.000Z", + kyc_tier=2, + ) + + response = self._call_node_harness("validate-feature-vector", vector) + self.assertTrue( + response.get("success"), + f"TypeScript rejected Python feature vector: {response.get('error')} {response.get('issues')}", + ) + + def test_python_gap_feature_vector_imputes_identically_in_typescript(self) -> None: + """Ensure Python missing-value policy aligns with TypeScript imputeFeatureVector.""" + builder = FeatureBuilder(default_kyc_tier=0) + gap_vector = builder.build_from_horizon( + account_id="account-gap-01", + account_data=None, + operations=None, + payments=None, + transactions=None, + observed_at="2026-01-15T00:00:00.000Z", + ) + + py_imputed, py_fields = impute_feature_vector(gap_vector) + + response = self._call_node_harness("impute-feature-vector", gap_vector) + self.assertTrue(response.get("success"), f"TypeScript impute failed: {response.get('error')}") + + ts_result = response["result"] + self.assertEqual(py_fields, ts_result["imputedFields"]) + self.assertEqual(py_imputed["medianSettlementLatencySeconds"], ts_result["medianSettlementLatencySeconds"]) + self.assertEqual(py_imputed["averageBalanceStroops"], ts_result["averageBalanceStroops"]) + + def test_python_generated_model_result_passes_typescript_validator(self) -> None: + """Ensure Python ModelResultEmitter output is accepted by TypeScript validateModelResult.""" + model = CreditScoreModel(random_state=42) + import numpy as np + + model.fit(np.random.rand(20, 10), np.random.randint(0, 2, size=20)) + + builder = FeatureBuilder(default_kyc_tier=1) + vector = builder.build_from_horizon( + account_id="account-scored-01", + observed_at="2026-01-15T00:00:00.000Z", + ) + + emitter = ModelResultEmitter() + result = emitter.score_and_emit( + model=model, + raw_or_imputed_vector=vector, + task="credit-score", + scored_at="2026-01-15T00:00:02.000Z", + ) + + response = self._call_node_harness("validate-model-result", result) + self.assertTrue( + response.get("success"), + f"TypeScript rejected Python model result: {response.get('error')} {response.get('issues')}", + ) + + def test_typescript_examples_pass_python_validators(self) -> None: + """Ensure TypeScript synthetic example fixtures validate cleanly under Python contracts.""" + cmd = ["node", str(self.harness_path), "export-examples"] + proc = subprocess.run(cmd, capture_output=True, text=True, check=True) + examples = json.loads(proc.stdout) + + ex_fv = examples["EXAMPLE_FEATURE_VECTOR"] + valid_fv = validate_feature_vector(ex_fv) + self.assertEqual(valid_fv["subjectId"], ex_fv["subjectId"]) + + ex_gaps = examples["EXAMPLE_FEATURE_VECTOR_WITH_GAPS"] + valid_gaps = validate_feature_vector(ex_gaps) + imputed, fields = impute_feature_vector(valid_gaps) + self.assertEqual(fields, ["accountAgeDays", "averageBalanceStroops", "medianSettlementLatencySeconds"]) + + ex_mr = examples["EXAMPLE_MODEL_RESULT"] + valid_mr = validate_model_result(ex_mr) + self.assertEqual(valid_mr["modelId"], ex_mr["modelId"]) + + ex_mr_imp = examples["EXAMPLE_MODEL_RESULT_WITH_IMPUTATION"] + valid_mr_imp = validate_model_result(ex_mr_imp) + self.assertEqual(valid_mr_imp["imputedFields"], ex_mr_imp["imputedFields"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/ml/tests/test_features.py b/ml/tests/test_features.py new file mode 100644 index 0000000..4fdae99 --- /dev/null +++ b/ml/tests/test_features.py @@ -0,0 +1,94 @@ +"""Unit tests for FeatureBuilder transformation logic.""" + +import unittest +from features import FeatureBuilder + + +class TestFeatureBuilder(unittest.TestCase): + """Test suite verifying FeatureVector construction from raw Horizon data.""" + + def setUp(self) -> None: + """Initialize FeatureBuilder instance.""" + self.builder = FeatureBuilder(default_kyc_tier=1) + + def test_build_from_complete_horizon_data(self) -> None: + """Ensure full Horizon history converts into a valid complete FeatureVector.""" + account_data = { + "balances": [ + {"asset_type": "credit_alphanum4", "balance": "500.0"}, + {"asset_type": "native", "balance": "250.75"}, + ] + } + operations = [ + {"created_at": "2025-01-15T00:00:00Z", "type": "create_account"}, + {"created_at": "2025-06-01T00:00:00Z", "type": "payment"}, + ] + payments = [ + { + "created_at": "2026-01-10T00:00:00Z", + "from": "GACC_TARGET", + "to": "GACC_OTHER1", + "amount": "50.0", + "asset_type": "native", + }, + { + "created_at": "2026-01-12T00:00:00Z", + "from": "GACC_OTHER2", + "to": "GACC_TARGET", + "amount": "100.0", + "asset_type": "credit_alphanum4", + }, + ] + transactions = [ + { + "created_at": "2026-01-10T00:00:00Z", + "successful": True, + "latency_seconds": 4.2, + }, + { + "created_at": "2026-01-11T00:00:00Z", + "successful": False, + "latency_seconds": 5.8, + }, + ] + + vector = self.builder.build_from_horizon( + account_id="GACC_TARGET", + account_data=account_data, + operations=operations, + payments=payments, + transactions=transactions, + observed_at="2026-01-15T00:00:00.000Z", + kyc_tier=2, + ) + + self.assertEqual(vector["subjectId"], "GACC_TARGET") + self.assertEqual(vector["kycTier"], 2) + self.assertEqual(vector["accountAgeDays"], 365) + self.assertEqual(vector["transactionCount30d"], 2) + self.assertEqual(vector["averageBalanceStroops"], 2507500000) + self.assertEqual(vector["largestTransferStroops"], 500000000) + self.assertEqual(vector["distinctCounterparties30d"], 2) + self.assertEqual(vector["failedPaymentCount90d"], 1) + self.assertEqual(vector["medianSettlementLatencySeconds"], 5.0) + + def test_build_with_missing_horizon_signals(self) -> None: + """Ensure missing signals produce explicit nulls for contract compliance.""" + vector = self.builder.build_from_horizon( + account_id="GACC_EMPTY", + account_data=None, + operations=None, + payments=None, + transactions=None, + observed_at="2026-01-15T00:00:00.000Z", + ) + + self.assertEqual(vector["subjectId"], "GACC_EMPTY") + self.assertIsNone(vector["accountAgeDays"]) + self.assertIsNone(vector["averageBalanceStroops"]) + self.assertIsNone(vector["transactionCount30d"]) + self.assertIsNone(vector["medianSettlementLatencySeconds"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/ml/tests/test_ingest.py b/ml/tests/test_ingest.py new file mode 100644 index 0000000..eb4cb8a --- /dev/null +++ b/ml/tests/test_ingest.py @@ -0,0 +1,71 @@ +"""Unit tests for StellarHorizonClient.""" + +import unittest +from unittest.mock import MagicMock +import requests + +from ingest import HorizonError, StellarHorizonClient + + +class TestStellarHorizonClient(unittest.TestCase): + """Test suite verifying Horizon client endpoints and error handling.""" + + def setUp(self) -> None: + """Create mock session for HTTP responses.""" + self.mock_session = MagicMock(spec=requests.Session) + self.client = StellarHorizonClient( + server_url="https://horizon-testnet.stellar.org", + session=self.mock_session, + ) + + def test_get_account_success(self) -> None: + """Verify successful account retrieval.""" + mock_resp = MagicMock() + mock_resp.status_code = 200 + mock_resp.json.return_value = {"id": "GACC_TEST", "balances": []} + self.mock_session.get.return_value = mock_resp + + acc = self.client.get_account("GACC_TEST") + self.assertEqual(acc["id"], "GACC_TEST") + + def test_get_account_not_found(self) -> None: + """Verify 404 status code raises HorizonError.""" + mock_resp = MagicMock() + mock_resp.status_code = 404 + self.mock_session.get.return_value = mock_resp + + with self.assertRaises(HorizonError) as ctx: + self.client.get_account("GACC_MISSING") + self.assertIn("not found on Horizon", str(ctx.exception)) + + def test_get_operations(self) -> None: + """Verify operations parsing from embedded records.""" + mock_resp = MagicMock() + mock_resp.status_code = 200 + mock_resp.json.return_value = {"_embedded": {"records": [{"id": "op-1"}]}} + self.mock_session.get.return_value = mock_resp + + ops = self.client.get_operations("GACC_TEST") + self.assertEqual(len(ops), 1) + self.assertEqual(ops[0]["id"], "op-1") + + def test_collect_account_data(self) -> None: + """Verify complete bundle collection.""" + mock_resp = MagicMock() + mock_resp.status_code = 200 + mock_resp.json.return_value = { + "id": "GACC_TEST", + "balances": [], + "_embedded": {"records": []}, + } + self.mock_session.get.return_value = mock_resp + + data = self.client.collect_account_data("GACC_TEST") + self.assertEqual(data["account_id"], "GACC_TEST") + self.assertIn("operations", data) + self.assertIn("payments", data) + self.assertIn("transactions", data) + + +if __name__ == "__main__": + unittest.main() diff --git a/ml/tests/test_models.py b/ml/tests/test_models.py new file mode 100644 index 0000000..1cd02ca --- /dev/null +++ b/ml/tests/test_models.py @@ -0,0 +1,86 @@ +"""Unit tests for CreditScoreModel and FraudDetectModel.""" + +import tempfile +import unittest +from pathlib import Path +import numpy as np + +from models import ( + CreditScoreModel, + FraudDetectModel, + compute_artifact_hash, + vector_to_features, +) + + +class TestModels(unittest.TestCase): + """Test suite verifying model training, inference, serialization, and digests.""" + + def setUp(self) -> None: + """Create deterministic dummy training data.""" + np.random.seed(42) + self.x_train = np.random.rand(50, 10) + self.y_train = np.random.randint(0, 2, size=50) + + def test_credit_score_model_inference_and_bounds(self) -> None: + """Verify credit scoring model outputs bounded scores and valid risk labels.""" + model = CreditScoreModel(random_state=42) + model.fit(self.x_train, self.y_train) + + test_row = list(self.x_train[0]) + score, label, confidence = model.predict_risk(test_row) + + self.assertTrue(0.0 <= score <= 1.0) + self.assertIn(label, ["low", "medium", "high"]) + self.assertTrue(0.0 <= confidence <= 1.0) + + def test_credit_score_model_persistence(self) -> None: + """Verify model serialization and 32-byte SHA-256 hash calculation.""" + model = CreditScoreModel(random_state=42) + model.fit(self.x_train, self.y_train) + + with tempfile.TemporaryDirectory() as tmpdir: + dest = Path(tmpdir) / "credit_model.joblib" + artifact_hash = model.save(dest) + self.assertEqual(len(artifact_hash), 64) + + loaded = CreditScoreModel().load(dest) + test_row = list(self.x_train[0]) + orig_score, _, _ = model.predict_risk(test_row) + loaded_score, _, _ = loaded.predict_risk(test_row) + self.assertEqual(orig_score, loaded_score) + + def test_fraud_detect_model_inference_and_bounds(self) -> None: + """Verify fraud detection model outputs bounded risk scores.""" + model = FraudDetectModel(random_state=42) + model.fit(self.x_train) + + test_row = list(self.x_train[0]) + score, label, confidence = model.predict_risk(test_row) + + self.assertTrue(0.0 <= score <= 1.0) + self.assertIn(label, ["low", "medium", "high"]) + self.assertTrue(0.0 <= confidence <= 1.0) + + def test_vector_to_features_ordering(self) -> None: + """Verify consistent feature vector translation to numeric array.""" + vector = { + "accountAgeDays": 100, + "transactionCount30d": 20, + "averageBalanceStroops": 5000000, + "largestTransferStroops": 1000000, + "distinctCounterparties30d": 3, + "failedPaymentCount90d": 1, + "disputeRatio90d": 0.05, + "crossBorderTransferRatio30d": 0.2, + "medianSettlementLatencySeconds": 12.0, + "kycTier": 1, + } + features = vector_to_features(vector) + self.assertEqual(len(features), 10) + self.assertEqual(features[0], 100.0) + self.assertEqual(features[-1], 1.0) + + +if __name__ == "__main__": + unittest.main() diff --git a/ml/tests/test_pipeline.py b/ml/tests/test_pipeline.py new file mode 100644 index 0000000..f2131e5 --- /dev/null +++ b/ml/tests/test_pipeline.py @@ -0,0 +1,70 @@ +"""Unit tests for the PipelineRunner coordinator.""" + +import json +import tempfile +import unittest +from pathlib import Path + +from pipeline import PipelineRunner +from sinks import FileDropSink + + +class TestPipeline(unittest.TestCase): + """Test suite verifying end-to-end pipeline execution.""" + + def setUp(self) -> None: + """Create sample feature vector payload.""" + self.sample_vector = { + "schemaVersion": "1.0.0", + "subjectId": "account-pipe-01", + "subjectKind": "account", + "observedAt": "2026-01-15T00:00:00.000Z", + "kycTier": 1, + "accountAgeDays": 120, + "transactionCount30d": 10, + "averageBalanceStroops": 50000000, + "largestTransferStroops": 10000000, + "distinctCounterparties30d": 4, + "failedPaymentCount90d": 0, + "disputeRatio90d": 0.0, + "crossBorderTransferRatio30d": 0.0, + "medianSettlementLatencySeconds": 5.0, + } + + def test_score_vector_all_tasks(self) -> None: + """Verify PipelineRunner scores both tasks and emits valid results.""" + with tempfile.TemporaryDirectory() as tmpdir: + output_file = Path(tmpdir) / "results.jsonl" + sink = FileDropSink(output_file) + runner = PipelineRunner(sink=sink) + + results = runner.score_vector(self.sample_vector, task="all") + self.assertEqual(len(results), 2) + tasks = {r["task"] for r in results} + self.assertEqual(tasks, {"credit-score", "fraud-detect"}) + + lines = output_file.read_text(encoding="utf-8").strip().splitlines() + self.assertEqual(len(lines), 2) + + def test_score_batch_file(self) -> None: + """Verify PipelineRunner processes batch files accurately.""" + with tempfile.TemporaryDirectory() as tmpdir: + input_file = Path(tmpdir) / "input.jsonl" + output_file = Path(tmpdir) / "output.jsonl" + + with open(input_file, "w", encoding="utf-8") as f: + f.write(json.dumps(self.sample_vector) + "\n") + vec2 = dict(self.sample_vector, subjectId="account-pipe-02") + f.write(json.dumps(vec2) + "\n") + + sink = FileDropSink(output_file) + runner = PipelineRunner(sink=sink) + results = runner.score_batch_file(input_file, task="credit-score") + + self.assertEqual(len(results), 2) + for r in results: + self.assertEqual(r["task"], "credit-score") + + +if __name__ == "__main__": + unittest.main() diff --git a/ml/tests/test_sinks.py b/ml/tests/test_sinks.py new file mode 100644 index 0000000..3e0bf93 --- /dev/null +++ b/ml/tests/test_sinks.py @@ -0,0 +1,85 @@ +"""Unit tests for FileDropSink, SqliteSink, and StdoutSink.""" + +import json +import sqlite3 +import tempfile +import unittest +from pathlib import Path + +from sinks import FileDropSink, SqliteSink, StdoutSink + + +class TestSinks(unittest.TestCase): + """Test suite for output sink persistence.""" + + def setUp(self) -> None: + """Create sample result dictionary.""" + self.sample_result = { + "schemaVersion": "1.0.0", + "modelId": "credit-score-gbm", + "modelVersion": "1.0.0", + "featureVectorVersion": "1.0.0", + "task": "credit-score", + "subjectId": "acc-sink-test-01", + "scoredAt": "2026-01-15T00:00:01.000Z", + "score": 0.12, + "label": "low", + "confidence": 0.88, + "imputedFields": [], + } + + def test_file_drop_sink_jsonl(self) -> None: + """Verify FileDropSink writes newline-delimited JSON records.""" + with tempfile.TemporaryDirectory() as tmpdir: + jsonl_path = Path(tmpdir) / "output.jsonl" + sink = FileDropSink(jsonl_path) + sink.write(self.sample_result) + sink.write(self.sample_result) + + lines = jsonl_path.read_text(encoding="utf-8").strip().splitlines() + self.assertEqual(len(lines), 2) + parsed = json.loads(lines[0]) + self.assertEqual(parsed["subjectId"], "acc-sink-test-01") + + def test_file_drop_sink_directory(self) -> None: + """Verify FileDropSink writes atomic individual files when directed at a directory.""" + with tempfile.TemporaryDirectory() as tmpdir: + drop_dir = Path(tmpdir) / "drops" + sink = FileDropSink(drop_dir) + sink.write(self.sample_result) + + expected_file = drop_dir / "acc-sink-test-01_credit-score.json" + self.assertTrue(expected_file.exists()) + with open(expected_file, "r", encoding="utf-8") as f: + data = json.load(f) + self.assertEqual(data["subjectId"], "acc-sink-test-01") + + def test_sqlite_sink_insert_and_query(self) -> None: + """Verify SqliteSink establishes schema and persists queried rows.""" + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + sink = SqliteSink(db_path) + sink.write(self.sample_result) + + with sqlite3.connect(db_path) as conn: + cursor = conn.cursor() + cursor.execute( + "SELECT subject_id, task, score, label FROM model_results WHERE subject_id = ?", + ("acc-sink-test-01",), + ) + rows = cursor.fetchall() + + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0][0], "acc-sink-test-01") + self.assertEqual(rows[0][1], "credit-score") + self.assertEqual(rows[0][2], 0.12) + self.assertEqual(rows[0][3], "low") + + def test_stdout_sink(self) -> None: + """Verify StdoutSink writes without unhandled exceptions.""" + sink = StdoutSink() + sink.write(self.sample_result) + + +if __name__ == "__main__": + unittest.main() diff --git a/ml/train.py b/ml/train.py new file mode 100644 index 0000000..a43dcf6 --- /dev/null +++ b/ml/train.py @@ -0,0 +1,163 @@ +"""Reproducible training script for credit scoring and fraud detection models. + +Trains baseline models with a deterministic random seed, persists model artifacts, +computes 32-byte SHA-256 artifact hashes for Soroban model-attestation compatibility, +and emits signed evaluation reports via evaluation_report.py. +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any, Dict, List, Tuple +import numpy as np +from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score, roc_auc_score +from sklearn.model_selection import train_test_split + +from contract import impute_feature_vector +from evaluation_report import create_evaluation_report, persist_report +from models import CreditScoreModel, FraudDetectModel, vector_to_features + + +def load_training_data(dataset_path: Path) -> Tuple[np.ndarray, np.ndarray, List[Dict[str, Any]]]: + """Load and prepare training feature vectors and binary targets.""" + with open(dataset_path, "r", encoding="utf-8") as f: + records = json.load(f) + + x_list: List[List[float]] = [] + y_list: List[int] = [] + cleaned_vectors: List[Dict[str, Any]] = [] + + for item in records: + target = item.get("_target_label", 0) + clean_item = {k: v for k, v in item.items() if not k.startswith("_")} + imputed_vec, _ = impute_feature_vector(clean_item) + features = vector_to_features(imputed_vec) + x_list.append(features) + y_list.append(int(target)) + cleaned_vectors.append(imputed_vec) + + return np.array(x_list, dtype=float), np.array(y_list, dtype=int), cleaned_vectors + + +def train_models( + dataset_path: Path, + output_dir: Path, + random_state: int = 42, +) -> Dict[str, Any]: + """Train credit scoring and fraud detection models and persist artifacts.""" + output_dir.mkdir(parents=True, exist_ok=True) + x_data, y_labels, _ = load_training_data(dataset_path) + + x_train, x_test, y_train, y_test = train_test_split( + x_data, y_labels, test_size=0.25, random_state=random_state, stratify=y_labels + ) + + credit_model = CreditScoreModel(random_state=random_state) + credit_model.fit(x_train, y_train) + credit_dest = output_dir / "credit_score_gbm.joblib" + credit_hash = credit_model.save(credit_dest) + + test_probs = [credit_model.predict_risk(row)[0] for row in x_test] + test_preds = [1 if p >= 0.5 else 0 for p in test_probs] + + acc = round(float(accuracy_score(y_test, test_preds)), 4) + prec = round(float(precision_score(y_test, test_preds, zero_division=0)), 4) + rec = round(float(recall_score(y_test, test_preds, zero_division=0)), 4) + f1 = round(float(f1_score(y_test, test_preds, zero_division=0)), 4) + roc = round(float(roc_auc_score(y_test, test_probs)), 4) + + credit_report = create_evaluation_report( + model={"name": credit_model.MODEL_ID, "version": credit_model.MODEL_VERSION}, + dataset={"identifier": "sample_dataset.json", "version": "1.0.0"}, + evaluation={"config_id": "eval-stratified-split", "params": {"test_size": 0.25, "seed": random_state}}, + metrics={"accuracy": acc, "precision": prec, "recall": rec, "f1_score": f1, "roc_auc": roc}, + code_version="1.0.0", + ) + persist_report( + credit_report, + output_dir / "credit_score_report", + metrics={"accuracy": acc, "precision": prec, "recall": rec, "f1_score": f1, "roc_auc": roc}, + ) + + fraud_model = FraudDetectModel(random_state=random_state) + fraud_model.fit(x_train) + fraud_dest = output_dir / "fraud_detect_iforest.joblib" + fraud_hash = fraud_model.save(fraud_dest) + + fraud_scores = [fraud_model.predict_risk(row)[0] for row in x_test] + anomaly_rate = round(float(np.mean([1 if s >= 0.5 else 0 for s in fraud_scores])), 4) + mean_score = round(float(np.mean(fraud_scores)), 4) + + fraud_report = create_evaluation_report( + model={"name": fraud_model.MODEL_ID, "version": fraud_model.MODEL_VERSION}, + dataset={"identifier": "sample_dataset.json", "version": "1.0.0"}, + evaluation={"config_id": "eval-isolation-forest", "params": {"contamination": 0.1, "seed": random_state}}, + metrics={"anomaly_rate": anomaly_rate, "mean_risk_score": mean_score}, + code_version="1.0.0", + ) + persist_report( + fraud_report, + output_dir / "fraud_detect_report", + metrics={"anomaly_rate": anomaly_rate, "mean_risk_score": mean_score}, + ) + + manifest = { + "models": { + credit_model.MODEL_ID: { + "version": credit_model.MODEL_VERSION, + "artifact": str(credit_dest.name), + "artifact_hash_sha256": credit_hash, + "report_id": credit_report["report_id"], + "attestation_bytes32": credit_hash, + }, + fraud_model.MODEL_ID: { + "version": fraud_model.MODEL_VERSION, + "artifact": str(fraud_dest.name), + "artifact_hash_sha256": fraud_hash, + "report_id": fraud_report["report_id"], + "attestation_bytes32": fraud_hash, + }, + }, + "random_state": random_state, + "dataset": str(dataset_path.name), + } + + manifest_path = output_dir / "manifest.json" + with open(manifest_path, "w", encoding="utf-8") as f: + json.dump(manifest, f, indent=2) + + return manifest + + +def main() -> None: + """CLI entrypoint for model training.""" + parser = argparse.ArgumentParser(description="Train chenaikit credit scoring and fraud detection models") + parser.add_argument( + "--dataset", + type=Path, + default=Path(__file__).resolve().parent / "data" / "sample_dataset.json", + help="Path to training dataset JSON", + ) + parser.add_argument( + "--output-dir", + type=Path, + default=Path(__file__).resolve().parent / "artifacts", + help="Output directory for serialized artifacts and evaluation reports", + ) + parser.add_argument( + "--seed", + type=int, + default=42, + help="Random state seed for reproducibility", + ) + args = parser.parse_args() + + manifest = train_models(args.dataset, args.output_dir, random_state=args.seed) + print("Training complete. Artifact manifest:") + print(json.dumps(manifest, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/packages/chenai-mlflow/src/index.test.ts b/packages/chenai-mlflow/src/index.test.ts index 4f57a78..a60e94c 100644 --- a/packages/chenai-mlflow/src/index.test.ts +++ b/packages/chenai-mlflow/src/index.test.ts @@ -153,6 +153,8 @@ describe("parsing and integrity", () => { it("rejects malformed JSON", () => { expect(() => parseEvaluationReport("{oops")).toThrow(ReportIntegrityError); + }); +}); import { EXAMPLE_FEATURE_VECTOR,