Metadata-Version: 2.4
Name: anchora-simlab
Version: 0.1.0
Summary: A thin, leakage-conscious bridge between polars time-series data and torch/JAX models.
Requires-Python: >=3.12
Requires-Dist: numpy>=1.26
Requires-Dist: polars>=1.0
Provides-Extra: jax
Requires-Dist: jax>=0.4; extra == 'jax'
Provides-Extra: torch
Requires-Dist: torch>=2.2; extra == 'torch'
Description-Content-Type: text/markdown

# anchora

A thin, leakage-conscious bridge between polars time-series data and torch/JAX models.

The library owns **data semantics** — alignment, target construction, splits, the
tensor boundary, and getting column names back at the end. You own the **model and
the training loop** entirely. The single invariant everything is organized around:

> Every row is an anchor: `t` is the moment the prediction is made. Features at
> anchor `t` may only use information from `≤ t`; targets at anchor `t` refer to
> information from `> t`.

## Quick tour

```python
from datetime import date
import anchora as ac

# Long-format canonical storage; wide frames are derived views.
frame = ac.TimeFrame.from_wide(df, time="time", freq="1d")

# The only source of date boundaries. Splits are by anchor (prediction) date.
split = ac.TimeSplit(train_end=date(2023, 1, 1), val_end=date(2024, 1, 1))

# Targets are declared compositions of invertible steps.
target = ac.chain(
    ac.horizon(5),                    # y(t+5), aligned to anchor t
    ac.delta(baseline="persistence"), # subtract y(t); inverse adds it back
    ac.scale("standard"),             # fitted strictly on the train part
)
built = target.build(frame, variables=["groundwater_level"], split=split)

# Feature engineering stays free-form polars, contract-checked at the boundary.
@ac.feature_fn
def make_features(wide: pl.DataFrame) -> pl.DataFrame:
    return wide.with_columns(pl.col("groundwater_level").shift(3).alias("groundwater lag3"))

features = make_features(frame.wide())

# The tensor boundary: numpy core, thin torch/JAX emitters, identity travels.
m = ac.Matrix(features, built, split, seq_len=30)
for x, y, idx in m.to_torch("train", batch_size=64):
    ...

# Model output back to a named, raw-space polars frame in one call.
preds = m.wrap_predictions(model_output, part="val")
m.worst(preds, k=5)          # ranked worst instances
m.inspect(date(2023, 6, 1))  # lookback window + target for one anchor
```

## Install

```
uv add anchora            # core: polars + numpy
uv add anchora[torch]     # torch emitter
uv add anchora[jax]       # jax emitter
```

## Documentation and examples

Concept docs live in [`docs/`](docs/index.md) (anchor convention, target
chains, the tensor boundary, contracts). Runnable examples with visual checks
live in [`examples/`](examples/) — the first three are pure
numpy/polars/matplotlib, the last two add the framework emitters:

```
uv run examples/01_frame_and_alignment.py   # the alignment convention, visually
uv run examples/02_target_chains.py         # chain stages + exact round trip
uv run examples/03_linear_model.py          # full loop with closed-form ridge
uv run examples/04_torch_lstm.py            # LSTM via to_torch (anchora[torch])
uv run examples/05_jax_mlp.py               # MLP via to_jax (anchora[jax])
uv run examples/06_forecast_value.py        # skill vs pseudo-forecast quality
```

## Design

The founding design spec lives in the JAXifer-harness repository
(`docs/superpowers/specs/2026-07-08-anchora-design.md`). Deliberately deferred:
the empirical leakage fuzzer (the `@feature_fn` registry is its replay seam)
and a general fit-window transform protocol (the `scale` target step is its
prototype).
