Metadata-Version: 2.4
Name: rudy-ab-test
Version: 0.1.0
Summary: Rule-based feature-flag and A/B test engine for Python
License-Expression: MIT
Project-URL: Homepage, https://github.com/RudyJ/rudy-ab-test
Project-URL: Issues, https://github.com/RudyJ/rudy-ab-test/issues
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: pytest; extra == "dev"

# rudy-ab-test

A lightweight, rule-based **feature-flag and A/B test engine** for Python.  
Define variables with fallback defaults, attach prioritized rules driven by arbitrary context dicts, and evaluate them at runtime — no external services required.

## Installation

```bash
pip install rudy-ab-test
```

## Quick start

```python
from rudy_ab_test import RuleEngine, Variable, Rule, Condition

engine = RuleEngine()

rule = Rule(
    name="premium_model_for_prod",
    conditions=[Condition(field="environment", operator="eq", value="prod")],
    result="gpt-4",
    priority=1,
)

engine.register(Variable(name="model_name", default="gpt-3.5-turbo", rules=[rule]))

result = engine.evaluate("model_name", {"environment": "prod"})
# → "gpt-4"
```

## Core concepts

### `Condition`

A single predicate evaluated against the context dict.

| Field      | Type  | Description                                    |
|------------|-------|------------------------------------------------|
| `field`    | `str` | Key looked up in the context dict              |
| `operator` | `str` | Comparison operator (see table below)          |
| `value`    | `Any` | Right-hand side of the comparison              |

#### Supported operators

| Operator      | Meaning                               |
|---------------|---------------------------------------|
| `eq`          | `context[field] == value`             |
| `neq`         | `context[field] != value`             |
| `in`          | `context[field] in value`             |
| `not_in`      | `context[field] not in value`         |
| `gt`          | `context[field] > value`              |
| `gte`         | `context[field] >= value`             |
| `lt`          | `context[field] < value`              |
| `lte`         | `context[field] <= value`             |
| `starts_with` | `str(context[field]).startswith(...)` |
| `ends_with`   | `str(context[field]).endswith(...)`   |

### `Rule`

A rule fires when **all** its conditions match. The first matching rule (by descending priority) determines the variable's value.

| Field        | Type           | Description                                |
|--------------|----------------|--------------------------------------------|
| `name`       | `str`          | Unique identifier (for debugging/logging)  |
| `conditions` | `list[Condition]` | All must match (AND logic)              |
| `result`     | `Any`          | Value returned when this rule fires        |
| `priority`   | `int`          | Higher value → evaluated first (default 0) |

### `Variable`

A named configuration point with a default value and an ordered list of rules.

| Field     | Type         | Description                              |
|-----------|--------------|------------------------------------------|
| `name`    | `str`        | Variable identifier                      |
| `default` | `Any`        | Returned when no rule matches            |
| `rules`   | `list[Rule]` | Evaluated in descending priority order   |

### `RuleEngine`

Central registry. Stores variables and evaluates them against a context dict.

## Usage

### Evaluating a single variable

```python
value = engine.evaluate("retriever_provider", context)
```

### Evaluating multiple variables at once

Pass a list of names to get a dict back:

```python
results = engine.evaluate(["retriever_provider", "model_name", "environment"], context)
# → {"retriever_provider": "aws", "model_name": "gpt-3.5-turbo", "environment": "dev"}
```

### Multiple conditions (AND logic)

All conditions in a rule must match for the rule to fire:

```python
rule = Rule(
    name="aws_for_application",
    conditions=[
        Condition(field="system_type", operator="eq",         value="application"),
        Condition(field="token_hash",  operator="starts_with", value="xpto"),
        Condition(field="token_hash",  operator="ends_with",   value="abc123"),
    ],
    result="aws",
    priority=1,
)
```

### Priority

Rules are sorted by descending `priority` on `register()`. When two rules could match, the one with the highest `priority` wins:

```python
rule_high = Rule(name="r1", conditions=[...], result="aws",    priority=2)
rule_low  = Rule(name="r2", conditions=[...], result="bridge", priority=1)
# rule_high is evaluated first
```

### Serialization — export and reload

Variables can be serialized to plain dicts (e.g. to store in a database or config file) and reloaded:

```python
payload = engine.export_variables()   # list[dict]
# ... save to DB / JSON file ...

new_engine = RuleEngine()
new_engine.load_variables(payload)
```

## Complete example

```python
from rudy_ab_test import RuleEngine, Variable, Rule, Condition

engine = RuleEngine()

# --- retriever_provider ---
engine.register(Variable(
    name="retriever_provider",
    default="bridge",
    rules=[
        Rule(
            name="aws_for_privileged_users",
            conditions=[
                Condition(field="system_type", operator="eq", value="user"),
                Condition(field="function",    operator="in", value=["SPECIALIST_1", "SUPERINTENDENT"]),
            ],
            result="aws",
            priority=2,
        ),
        Rule(
            name="aws_for_verified_apps",
            conditions=[
                Condition(field="system_type", operator="eq",          value="application"),
                Condition(field="token_hash",  operator="starts_with", value="xpto"),
                Condition(field="token_hash",  operator="ends_with",   value="abc123"),
            ],
            result="aws",
            priority=1,
        ),
    ],
))

# --- model_name ---
engine.register(Variable(
    name="model_name",
    default="gpt-3.5-turbo",
    rules=[
        Rule(name="prod_model", conditions=[Condition(field="environment", operator="eq", value="prod")], result="gpt-4",         priority=1),
        Rule(name="dev_model",  conditions=[Condition(field="environment", operator="eq", value="dev")],  result="gpt-3.5-turbo", priority=2),
    ],
))

# Evaluate a privileged user
user_ctx = {"system_type": "user", "function": "SPECIALIST_1", "environment": "dev"}
print(engine.evaluate(["retriever_provider", "model_name"], user_ctx))
# → {"retriever_provider": "aws", "model_name": "gpt-3.5-turbo"}

# Evaluate a standard user
anon_ctx = {"system_type": "user", "function": "JUNIOR_ANALIST", "environment": "dev"}
print(engine.evaluate(["retriever_provider", "model_name"], anon_ctx))
# → {"retriever_provider": "bridge", "model_name": "gpt-3.5-turbo"}
```

## Requirements

- Python ≥ 3.10

## License

MIT
