Metadata-Version: 2.5
Name: welt-client
Version: 0.10.0
Summary: Welt Python client and scikit-learn estimators
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: <3.14,>=3.11
Requires-Dist: httpx<1,>=0.28
Requires-Dist: numpy<3,>=2
Requires-Dist: pandas<4,>=2.2
Requires-Dist: scikit-learn<2,>=1.6
Provides-Extra: parquet
Requires-Dist: pyarrow<30,>=19; extra == 'parquet'
Provides-Extra: research
Requires-Dist: pandas<4,>=2.2; extra == 'research'
Requires-Dist: pyarrow<30,>=19; extra == 'research'
Description-Content-Type: text/markdown

# Welt Python SDK

From a DataFrame to a prediction. Use familiar `Classifier` and `Regressor`
estimators backed by durable predictors in your private Welt workspace.
Install `welt-client`, import `welt`. Python 3.11–3.13.

[Start here](https://ergodic-ai.github.io/welt-python/) ·
[Guides](https://ergodic-ai.github.io/welt-python/v0.8.0/own-data.html) ·
[Reference](https://ergodic-ai.github.io/welt-python/v0.8.0/reference.html) ·
[Models and limits](docs/capabilities.md)

## Install

```sh
python -m pip install "welt-client[parquet]==0.8.0"
```

Run `welt login` and approve your workspace in the browser. This saves a private,
origin-bound credential for later Python sessions. In a notebook, connect once:

```python
from welt import Client

with Client() as client:
    client.connect()
```

Open its printed verification link on your own computer if Python is remote.
Python connection stays in this process; use `save=True` explicitly to persist it.
Constructors never open a browser. Explicit keys and `WELT_API_KEY` still work and
have precedence over connected/saved credentials. [Connection guide](docs/connect.md).

## Learn with real data

Download Iris from our research inventory, look at the table, split train/test,
fit a classifier, predict unseen rows, and measure the result:

```python
from pathlib import Path
from tempfile import mkdtemp
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
from welt import Client, Classifier

# 1. Get a real table from Welt's research inventory.
dataset_id = "asset-d839644b6de36fc5cafdda18c5daeca3"
version = "d839644b6de36fc5cafdda18c5daeca3284a5ce8bb6169aff2fcb3fbb4a12021"
path = Path(mkdtemp(prefix="welt-iris-")) / "iris.parquet"
with Client() as client:
    info = client.research_dataset(dataset_id)
    print("Dataset:", info["name"], "— License:", info["license"])
    for notice in info["license_notices"]:
        print(notice)
    client.download_research_dataset(dataset_id, path, version=version)
data = pd.read_parquet(path)
target = "__target__#0"  # The target recorded in this research snapshot.

# 2. Look at the rows before modelling.
print(data.head())
print("Rows and columns:", data.shape)
data.info()
print(data[target].value_counts())

# 3. Set aside test rows before fitting; never train on their answers.
train, test = train_test_split(
    data, test_size=0.2, random_state=9, stratify=data[target])
query = test.drop(columns=target)
y_test = test[target]
print("Training rows:", len(train), "— Test rows:", len(test))

# 4. Create a model and train it on the training table.
model = Classifier(model="tabicl-v2", random_state=9)
model.fit(train, target=target)

# 5. Predict the held-out rows, without passing their target column.
predictions = model.predict(query)
print(pd.DataFrame({"actual": y_test.to_numpy(), "predicted": predictions}).head())

# 6. Compare predictions with the answers we kept aside.
print("Test accuracy:", accuracy_score(y_test, predictions))
print(classification_report(y_test, predictions, zero_division=0))
print("Keep this predictor ID:", model.predictor_id_)
```

Accuracy and per-class precision/recall/F1 describe your held-out run, not a
promised benchmark score. [Start](docs/start.md) explains each step and teaches
numeric predictions with real Yacht data. `fit` uploads only your training rows;
your workspace retains the resulting dataset and predictor for reuse.

### Research metadata and one-step loading

Keep the catalogue detail (licence, schema and column descriptions, provenance)
beside a download with `metadata=True`, or load a table directly with the
`research` extra (`pip install "welt-client[research]"`):

```python
with Client() as client:
    saved = client.download_research_dataset(
        dataset_id, path.with_name("iris-2.parquet"), version=version, metadata=True)
    # saved.path, saved.metadata_path ("iris-2.parquet.metadata.json"), saved.metadata
    data, meta = client.load_research_dataset(dataset_id, version=version, metadata=True)
print(meta.name, meta.column_descriptions)
```

Without `metadata=True` the download still returns the path, unchanged. The pinned
Iris `version` keeps working if a newer (restored-header) version becomes current:
the SDK downloads any version listed in the detail's `previous_content`.
`load_research_dataset` removes its private temporary file; for notice-bearing ZIP
content it warns that `meta.license_text`/`meta.attribution_text` apply. See
[Research datasets](docs/research-datasets.md).

SDK 0.7 adds explicit DataFrame target convenience and hosted credential guidance
while preserving `fit(X, y)`, sync/async resources, durable jobs, research downloads
and native causal objects. Available hosted tasks and workers follow the service
catalogue. Bounded TabICLv2, Kumo Medium, TabDPT1.3 and Mitra-v2 classification and
regression previews are separately qualified: 500 training rows, 20 features,
100 query rows, and 10 classes for classification. Other tasks need separate
qualification. File reads load into memory. Held resumable CSV and large batch
APIs are excluded.

A local wait timeout leaves server work running. Reconnect by its job ID rather
than fitting again; [recovery](docs/async-and-errors.md) describes typed failures,
authentication, cancellation and retry behavior. Probabilities are not calibrated
coverage. ArrowFM, CDFM and AVICI discovery preserve native graph and score semantics;
[causal conversion](docs/causal-discovery.md) is restricted to declared native DAGs
and the separately approved Ergodic co-release artifact. Public Ergodic distribution
and the complete all-model catalogue remain gated.

## Examples and development

[Runnable script](examples/first_prediction.py) · [Jupyter notebooks](docs/notebooks.md) ·
[Research datasets](docs/research-datasets.md) · [Contributing](CONTRIBUTING.md)

```sh
uv sync --frozen
uv run --frozen pytest -q
uv build
```

Offline tests patch HTTP transport outside user examples; they neither contact the
hosted service nor measure foundation-model quality. Notebook sources have cleared outputs; clean-wheel execution and
bounded authorized live acceptance are separate evidence. CI requires no private
backend, credentials or checkpoints.

Apache 2.0: see [LICENSE](LICENSE). Model weights and hosted service are separate
from this SDK licence.

Agent workflows, workspace selection and the full source-candidate command reference: [CLI](docs/cli.md).

## Explicit hidden-model runs

With SDK 0.10, `client.model("chronos-2")` inspects an explicit hidden ID while
`client.models()` keeps the main catalogue. `client.submit_model_run(payload,
idempotency_key="your-key")` returns a durable Job; `job.result()` returns the
native task result. AsyncClient has matching methods. The payload follows the
API's tabular support/query, causal table or univariate forecasting contract;
check the model metadata for enabled tasks, limits and licence access.

```python
from welt import Client
with Client() as client:
    job = client.submit_model_run({
        "model_id": "chronos-2", "task": "univariate_forecasting",
        "series": {"values": [float(i) for i in range(48)],
                   "start": "2026-01-01T00:00:00Z", "frequency_seconds": 3600},
        "horizon": 12,
    }, idempotency_key="forecast-example-1")
    result = job.result()
    print(result["predictions"])
```

CLI equivalents: `welt models get chronos-2`, `welt run --input request.json`,
`welt jobs wait JOB_ID`. Use `welt model-runs delete JOB_ID --yes` or
`client.delete_model_run(id)` to explicitly remove a terminal run's input/result.
Deleting keeps the idempotency/usage record; a new run needs a fresh key. Native
model libraries remain server-side. Restricted models need an internal testing
workspace; hidden status alone does not grant serving rights. TabSTAR's trained
state stays alive for the single run, and these runs do not publish reusable
support-only fitted predictors. LimiX query splitting can change its transductive
results. API errors, cancellation and timeouts retain existing typed semantics.
