# ablevlabs

Small, friendly Python tools by **AbleVLabs** - each one takes something that
usually feels intimidating and makes it feel easy.

Two tools live in the package today: **kami**, a machine-learning tutor that
builds your model *and* explains what it means in plain English, and **lana**, a
weighted matchup engine that decides which of two or more records comes out on top.

---

## kami - your machine-learning tutor

You bring the data. kami brings the patience.

Most machine-learning tools hand you a number and walk away. **kami stays.** It
cleans your messy spreadsheet, trains a solid model, quietly catches the mistakes
that trip up every beginner - and then it turns around and *explains the whole
thing to you* in plain English, like a good tutor leaning over your shoulder.

Three lines:

```python
from ablevlabs import kami

df = kami.load("houses.csv")              # read it
df = kami.clean(df)                        # tidy it
result = kami.train(df, target="price")    # learn from it
```

...and kami talks back:

```
   ======================================================================
   WHAT KAMI FOUND
   ======================================================================
   Question      Can 'price' be predicted from the other columns?
   Answer        Yes - and quite well.

   How good      R2 = 0.97. In plain terms, the model explains about 97% of
                 why 'price' changes from one row to the next.
   Typical miss  the model is usually off by about 17324 (e.g. predicted
                 159179, actual 173500).
   Verdict       [*****] Excellent
   Confidence    Moderate - 60 unseen test rows.

   What we learned:
     - 'size_sqft' and 'age_years' look most related to 'price' (related to,
       not necessarily the cause).
     - There's real signal here - your columns do help predict 'price'.

   Do next       kami.feature_importance(result)
   ======================================================================
```

No jargon to google. No charts to squint at. Just the four things that matter:
what's the question, how good is the answer, how much should you trust it, and
what to do next.

**[Open the full kami guide ->](docs/kami.md)**

---

## lana - Logical Attribute Node Aligner

Point `lana` at two records and it weighs them attribute by attribute, then
tells you which one wins. It works on anything with comparable fields - job
candidates, products, ML model runs, vehicles, portfolios, game characters.

Unlike a *diff* tool (which tells you what changed between two versions of the
same thing), `lana` is a *matchup* tool: it relates two distinct records and
renders a weighted verdict based on the fields **you** decide matter most.

### Install

```bash
pip install ablevlabs
```

Optional, for the DataFrame export:

```bash
pip install "ablevlabs[pandas]"
```

### Quick start

```python
from ablevlabs import lana

laptop_a = {"name": "Aero 14",  "price": 1200, "ram_gb": 16, "battery_hrs": 10}
laptop_b = {"name": "Vortex 15", "price": 1500, "ram_gb": 32, "battery_hrs": 8}

result = lana.compare(
    laptop_a, laptop_b,
    priority={"ram_gb": 5, "battery_hrs": 2, "price": 2},  # 1-5: how much each matters
    lower_better=["price"],                                # cheaper wins
)

lana.show(result)
```

```
  Aero 14  vs  Vortex 15
  --------------------------------------------------
  battery_hrs           10 <-- 8          (priority 2)
  name             Aero 14 !=  Vortex 15
  price               1200 <-- 1500       (priority 2)
  ram_gb                16 --> 32         (priority 5)
  --------------------------------------------------
  OVERALL: Vortex 15 wins   (Aero 14 44.4%  /  Vortex 15 55.6%)
```

The Aero wins two fields (battery, price) - but they're lower priority. The
Vortex wins only RAM, but RAM is rated 5, and that one high-priority win
carries the verdict. That is the whole point of `lana`.

### How the verdict works

Each field you list in `priority` is rated **1-5** (1 = barely matters,
5 = matters most). Whichever record wins a field takes its full priority
points; a tie splits them. The record with more total points wins overall.
The per-field margins are shown as `delta`, but they do **not** secretly sway
the verdict - winning a field is winning a field.

### `compare()` options

| Argument | What it does |
|---|---|
| `a`, `b` | The two records (dicts). Required. |
| `name_a`, `name_b` | Labels for the output. If omitted, lana reads a `name`/`model`/`id` field, else uses `A`/`B`. |
| `priority` | `{field: 1-5}` - how much each numeric field counts toward the winner. |
| `lower_better` | List of fields where a **smaller** number wins (price, latency, weight). |
| `aliases` | `{variant: canonical}` to reconcile differing field names between records. |

### Ranking many items - `rank()`

`compare()` is head-to-head. To rank **three or more** records into a
leaderboard, use `rank()`:

```python
from ablevlabs import lana

fighters = [
    {"name": "Goku",    "power_level": 9100,  "attack": 22, "defense": 14},
    {"name": "Vegeta",  "power_level": 9000,  "attack": 25, "defense": 10},
    {"name": "Frieza",  "power_level": 10500, "attack": 24, "defense": 16},
]

board = lana.rank(fighters, priority={"power_level": 5, "attack": 4, "defense": 3})
lana.show(board)
```

```
  LEADERBOARD
  ------------------------------------------
  * 1. Frieza             10.0 pts
    2. Vegeta              4.33 pts
    3. Goku               1.67 pts
```

Pass a **list** of records (names are read from each record's
`name`/`model`/`id` field) or a **`{name: record}` dict**. Scoring is
*rank-weighted*: on each field, items earn points by placement - best gets
the field's full priority, worst gets 0, evenly spaced; ties split the
average. For exactly two items, `rank()` matches `compare()`. If you omit
`priority`, every numeric field shared by all items counts equally.

All the exporters below (`show`, `to_json`, `to_csv`, `to_markdown`,
`to_df`, `save`) work on `rank()` results too.

### Getting the result out

`compare()` returns a plain dict. From there:

```python
lana.show(result)         # readable scorecard (terminal)
lana.to_json(result)      # JSON string
lana.to_csv(result)       # CSV string (Excel / Sheets)
lana.to_markdown(result)  # Markdown table (docs / GitHub)
lana.to_df(result)        # pandas DataFrame (needs pandas)

lana.save(result, "out.json")   # format chosen from the extension
lana.save(result, "out.csv")
lana.save(result, "out.md")
```

### Nested fields

If a field holds a dict, `lana` compares inside it - shared keys get a winner,
and keys unique to one side are flagged:

```python
a = {"stats": {"hp": 100, "atk": 20}}
b = {"stats": {"hp": 80,  "atk": 30, "def": 5}}
lana.show(lana.compare(a, b, "A", "B"))
```

### Guardrails

- `priority` values must be **1-5**; anything else raises a clear `ValueError`.
- Misspelled `priority`/`lower_better` field names emit a warning instead of
  silently doing nothing.
- Booleans are treated as text, not numbers (so `True`/`False` aren't scored
  as `1`/`0`).

---

## License

MIT (c) 2026 Carlos Able Vivanco / AbleVLabs - see [LICENSE](LICENSE).