Metadata-Version: 2.4
Name: keble-referral
Version: 1.1.4
Author-email: zhenhao-ma <bob0103779@gmail.com>
Requires-Python: <3.14,>=3.13
Requires-Dist: keble-db<2.0.0,>=1.10.0
Requires-Dist: keble-exceptions<1.0.0,>=0,>=0.0.9
Requires-Dist: keble-helpers<2.0.0,>=1.54.0
Requires-Dist: keble-payment<3.0.0,>=2.4.1
Description-Content-Type: text/markdown

# keble-referral

`keble-referral` owns referral-code, referrer, wallet, policy, commission, and
withdrawal-history schemas plus the Mongo/Redis-backed `ReferralClient`.

## Exact Money Contract

`keble-referral 1.1.4` interprets payment captures, commissions, wallet
balances, locked balances, and withdrawal request integers as the wallet
currency's ISO minor units. Percentage commissions use exact Decimal basis-point
math with one explicit round-down to prevent over-credit. All stored and
response Money values use the shared exponent (JPY 0, USD 2, BHD 3); no wallet
path divides by 100 or rebuilds a float.

Withdrawal amounts must be strictly positive canonical minor units; invalid
zero or negative requests fail schema validation before any wallet mutation or
provider call.

Referral policy threshold and fixed-commission currencies must match the
policy marketplace currency. Invalid policies fail at schema admission rather
than later during a paid callback or wallet tier comparison.

## Mongo Startup Index Contract

`ReferralClient.ainit(amongo=...)` creates idempotent Mongo indexes for the
backend-used referral reads: owner/referrer lookups, referral code lookups,
wallet by marketplace, withdrawal history by wallet/status/transaction, paid
history by referrer/referee/wallet/order, and marketplace policy reads.

Side effect if changes: `keble.backend.app.main.init_db()` calls this hook
before APIs/workers serve traffic; do not create these indexes inside request
handlers or CRUD hot paths.

## Testing

This repo follows the root `TESTING_GUIDELINE.md`.
Detailed test structure notes live in `docs/testing.md`.

Local-full development command:

```bash
RUN_INTEGRATION=1 RUN_REAL_DB=1 RUN_LOCAL_STACK=1 RUN_DB_STACK=1 \
  uv run pytest -q -m "not live and not container"
```

Portable-offline command:

```bash
uv run pytest -q -m "not live and not slow and not eval and not local_stack and not db_stack and not container"
```

Run schema-only unit tests:

```bash
uv run pytest -m unit
```

Run real Mongo/Redis integration tests:

```bash
RUN_INTEGRATION=1 RUN_REAL_DB=1 RUN_LOCAL_STACK=1 uv run pytest -q -m integration
```

Eval selector:

```bash
uv run pytest -q -m eval
```

Integration tests require:

```text
MONGO_DB_URI or MONGO_TEST_URI
REDIS_URI or REDIS_TEST_URI
```

The test settings resolve those aliases through `keble_db.testing.resolve_test_env`,
so clean worktrees can reuse credentials from the umbrella `keble.backend/.env`
without committing package-local secrets.

Run the Python syntax/type gate:

```bash
npx --yes pyright .
```

Latest 2026-07-18 proof: exact Money edge slice `11 passed`; portable
`70 passed, 284 deselected`; isolated local-full `355 passed`; isolated
integration `284 passed, 71 deselected`; Pyright `0 errors`; Ruff, lock, and
wheel/sdist build gates passed.

Layer mapping:

- `tests/unit/schemas/` is the fast `unit` layer when tests do not request DB fixtures.
- `tests/integration/crud/`, `tests/integration/main/`, and `tests/integration/workflows/` are guarded `integration`, `db`, `mongo`, `redis`, and `local_stack` tests.
- Future referral quality or policy-regression datasets belong under `tests/evals` with `eval`; selecting `-m eval` is enough, so do not add a generic `RUN_EVALS` gate.
- Mongo databases, collection names, and Redis namespaces are generated per test with `keble_db.testing.create_test_namespace`; do not reintroduce broad shared DB cleanup.
- External withdrawal execution remains mocked by default; do not add real payment-provider calls to default tests.
