Metadata-Version: 2.4
Name: ablevlabs
Version: 0.2.0
Summary: AbleVLabs tools. Includes lana, a weighted record-comparison engine that weighs two records attribute by attribute and tells you which wins.
Author: AbleVLabs
License: MIT
Project-URL: Homepage, https://ablevlabs.com
Project-URL: Repository, https://github.com/AbleVLabs
Keywords: comparison,compare,records,json,weighted,matchup,data,analysis
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: pandas
Requires-Dist: pandas; extra == "pandas"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pandas; extra == "dev"
Requires-Dist: build; extra == "dev"
Dynamic: license-file

# ablevlabs

Python tools by **AbleVLabs**. The first tool in the package is **lana**.

---

## 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 © AbleVLabs
