Metadata-Version: 2.4
Name: qarar
Version: 1.0.2
Summary: QARAR: Arabic-first typed-decision models (calibrated distributions over supplied options).
Author: NOMEDA
License-Expression: Apache-2.0
Project-URL: Homepage, https://huggingface.co/nomeda-lab/nomeda-decision-v0.1-beta
Keywords: arabic,nlp,typed-decisions,calibration,probability-estimation
Classifier: Programming Language :: Python :: 3
Classifier: Natural Language :: Arabic
Classifier: Natural Language :: English
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: torch>=2.2
Requires-Dist: transformers>=4.44
Requires-Dist: safetensors>=0.4
Requires-Dist: huggingface_hub>=0.23
Requires-Dist: numpy>=1.24
Requires-Dist: PyYAML
Provides-Extra: data
Requires-Dist: datasets>=2.18; extra == "data"
Requires-Dist: pandas>=2.0; extra == "data"
Requires-Dist: pyarrow>=14; extra == "data"
Provides-Extra: train
Requires-Dist: sentence-transformers>=3.0; extra == "train"
Requires-Dist: regex; extra == "train"
Requires-Dist: pydantic>=2.5; extra == "train"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"

# NOMEDA-Decision v1

Arabic-first **typed-decision** model. Given one *state* it answers **many independent
questions** by returning a calibrated probability distribution over the **supplied
options** — it never generates text.

## Install

```bash
pip install qarar
```

```python
from qarar import load          # published alias; `from nomeda import load` also works
m = load("nomeda-lab/qarar")
```

Question types share one scoring path:

| type | meaning | options |
|---|---|---|
| `choice` | pick one named option | K named options |
| `score` | rank/level a target | K ordered options (+ `rank_emb`) |
| `noul` | yes / no / unknown | 2 options (`false`, `true`) |

- Encoder: `silma-ai/silma-embedding-matryoshka-v0.1` (Arabetv02 backbone, D=768, mean
  pooling), loaded via `AutoModel`/`AutoTokenizer` (no `trust_remote_code`).
- Non-interference is **exact**: each question cross-attends only to the shared state,
  never to another question (block-diagonal attention).
- Encoder calls per request ≤ 2: state once, all question/option texts in one batched pass.

See `docs/MODEL_REPORT.md` for the architecture, measured parameter budget, latency and
readiness. Diagram: `docs/nomeda_decision_architecture.drawio`.

## Layout

```
nomeda/            library
  config.py        NomedaConfig
  encoder.py       SilmaEncoder (mean pooling, chunking, LRU cache)
  blocks.py        DecisionBlock / OptionStateBlock / AttnPool / CompatibilityScorer
  model.py         NomedaModel + param_report()
  runtime.py       NomedaRuntime.predict / predict_batch, validate_questions
  loss.py          proper-scoring losses
  calibrate.py     temperature scaling, abstention thresholds, ECE
  data/            loader, batching/augmentation, family_map.yaml
scripts/           audit_data, train, calibrate, evaluate, export, bench_latency
tests/             invariance, runtime, leakage, pooling-parity, determinism
configs/           default.yaml
docs/              MODEL_REPORT, DATA_AUDIT, VERIFIED, OPEN_QUESTIONS, RESULTS
```

## Install

```bash
pip install nomeda                       # from PyPI (v1.0.1+)
# or, for development, from the repository root:
pip install -e .
# with the data-prep and training extras:
pip install -e ".[data,train,dev]"
```

`pip install nomeda` (or `pip install -e .`) makes `import nomeda` work from any
directory.

## Usage

**Recommended — load the published weights (calibration included):**

```python
from nomeda import load

nm = load("nomeda-lab/nomeda-decision-v0.1-beta")   # or a local export directory
out = nm.predict(
    state="أنا اتخصم مني مرتين على فاتورة مارس، عايز أرجّع الفلوس النهاردة.",
    questions={
        "intent": {"type": "choice", "instructions": "ماذا يريد العميل؟",
                   "criteria": {"refund": "استرداد المال",
                                "technical_help": "مشكلة تقنية",
                                "cancellation": "إلغاء الخدمة"}},
        "frustration": {"type": "score", "instructions": "ما مدى انزعاج العميل؟",
                        "criteria": ["هادئ", "قلق", "منزعج", "غاضب جدًا"]},
        "refund_requested": {"type": "noul", "instructions": "هل يطلب العميل استرداد أمواله؟"},
    },
)

out["answers"]["intent"]["choice"]           # -> "refund"
out["answers"]["intent"]["probabilities"]    # -> {"refund": 0.555, ...}
out["answers"]["refund_requested"]["noul"]   # -> 0.608
```

`load()` fetches `temperatures.json` and `thresholds.json` alongside the weights,
so predictions are calibrated and abstention works. **This is what you want
almost always.**

**Manual construction** (when you have the weights and calibration already and
want full control):

```python
import json
from nomeda import NomedaConfig, NomedaModel
from nomeda.runtime import NomedaRuntime

model = NomedaModel(NomedaConfig(), device="cpu")        # or device="cuda"

# temperatures/thresholds are REAL files, not placeholders. Load them:
temperatures = json.load(open("temperatures.json"))       # {type: {bucket: T}}
thresholds   = json.load(open("thresholds.json"))         # {type: p_min}

rt = NomedaRuntime(model, temperatures=temperatures,
                   thresholds=thresholds, device="cpu")
rt.predict(state, questions)
```

Without calibration pass `temperatures={}, thresholds={}` (identity temperature,
no abstention) — never `...`, which raises a `TypeError`.

> **Naming.** The classes were `Nomed*` in the v0.1-beta release. `NomedConfig`,
> `NomedModel`, `NomedRuntime` and `Nomed` remain available as aliases, so existing
> code keeps working; new code should use the `Nomeda*` names.

## Pipeline

```bash
DATA=/path/to/data/final_training        # train.jsonl, validation.jsonl, test.jsonl
python scripts/train.py       --data-dir $DATA --out checkpoints/run
python scripts/calibrate.py   --data-dir $DATA --checkpoint checkpoints/run/checkpoint.pt --out artifacts
python scripts/evaluate.py    --data-dir $DATA --checkpoint checkpoints/run/checkpoint.pt --artifacts artifacts --out docs/RESULTS.md
python scripts/export.py      --checkpoint checkpoints/run/checkpoint.pt --artifacts artifacts --out dist/nomeda-decision
```

## Tests

```bash
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest tests -m "not slow"   # fast suite
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest tests -m "slow"       # real-encoder parity
```

## Kaggle

`kaggle/nomeda_decision_kaggle.ipynb` runs the whole frozen-encoder pipeline (feature
cache → baselines → training → calibration → test → export/handoff) on a Kaggle GPU.
Upload the repo as a zip dataset and the `*.jsonl` data as another dataset, attach both,
enable GPU + Internet, and run the cells top to bottom. See the notebook's first cell.

## License

Code: see `LICENSES.yaml` / repository. Encoder `silma-embedding-matryoshka-v0.1` is
Apache-2.0 (model card; no standalone LICENSE file upstream).
