Metadata-Version: 2.4
Name: kurversc
Version: 0.2.0
Summary: Validation-guided configuration search for GraphReduce
Author: KurveAI
License-Expression: MIT
Project-URL: Homepage, https://kurve.ai/research
Project-URL: Blog, https://kurve.ai/blog/kurversc-learning-the-shape-of-relational-signal
Project-URL: Repository, https://github.com/kurveai/kurversc
Project-URL: Documentation, https://github.com/kurveai/kurversc/blob/main/docs/kurversc-staged-technical-report.md
Project-URL: Issues, https://github.com/kurveai/kurversc/issues
Requires-Python: <3.13,>=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: catboost<2,>=1.2
Requires-Dist: duckdb<1.3,>=1.2
Requires-Dist: graphreduce<1.11,>=1.10.18
Requires-Dist: numpy<2,>=1.24
Requires-Dist: pandas<3.1,>=1.5
Requires-Dist: pytz>=2024.1
Requires-Dist: scikit-learn<2,>=1.4
Requires-Dist: structlog>=23.1
Provides-Extra: test
Requires-Dist: pytest<9,>=8; extra == "test"
Provides-Extra: relbench
Requires-Dist: relbench<3,>=2.1; extra == "relbench"
Provides-Extra: tabicl
Requires-Dist: tabicl<3,>=2.1; extra == "tabicl"
Provides-Extra: tabpfn
Requires-Dist: tabpfn<10,>=8.5; extra == "tabpfn"
Provides-Extra: tabfm
Requires-Dist: tabfm<1,>=0.1; extra == "tabfm"
Provides-Extra: staged
Requires-Dist: graphreduce==1.10.18; extra == "staged"
Requires-Dist: catboost==1.2.10; extra == "staged"
Requires-Dist: duckdb==1.2.2; extra == "staged"
Requires-Dist: numpy==1.26.4; extra == "staged"
Requires-Dist: pandas==2.3.3; extra == "staged"
Requires-Dist: scikit-learn==1.6.1; extra == "staged"
Requires-Dist: relbench==2.1.2; extra == "staged"
Requires-Dist: psutil<8,>=5.9; extra == "staged"
Requires-Dist: pyarrow==20.0.0; extra == "staged"
Dynamic: license-file

# KurveRSC

> The narrowed staged submission is prepared for independent maintainer
> reproduction. `kurversc.run_staged_task(...)` / `kurversc-staged` retains graph
> search, finalist and temporal-policy selection, and one generic readout menu.
> See [the revised procedure](docs/staged-release.md) and
> [reproduction revision](docs/generic-staged-review.md#source-graph-replay-correction).
> The [six-task pilot](docs/generic-staged-pilot.md) measures the preceding
> narrowed revision. Fresh full-panel results and a verified Elo remain pending.
> The October 27 tables and technical report describe the superseded recipe.

KurveRSC is an integrated relational representation and model-selection
system. It uses [GraphReduce](https://github.com/wesmadrigal/graphreduce) as
its relational feature engine, searches graph configurations
across temporal frames, and selects them by downstream validation performance.

<p align="center">
  <a href="https://kurve.ai/research">Kurve Research</a> ·
  <a href="https://kurve.ai/blog/kurversc-learning-the-shape-of-relational-signal">KurveRSC Article</a> ·
  <a href="docs/staged-release.md">Current Procedure</a> ·
  <a href="docs/kurversc-staged-technical-report.pdf">Historical Report</a> ·
  <a href="#citation">Citation</a> ·
  <a href="https://pypi.org/project/kurversc/">PyPI</a>
</p>

<p align="center">
  <img
    src="https://raw.githubusercontent.com/kurveai/kurversc/main/docs/assets/kurversc-shape-selection.svg"
    alt="KurveRSC materializes relational feature frames with different shapes, evaluates them jointly with a downstream learner, and selects one frozen graph configuration for final fitting."
    width="1000"
  />
</p>

<p align="center"><em>KurveRSC searches the relational signal-compression space and fits the selected shape on full point-in-time data.</em></p>

> **Thesis.** Relational signal compression is a first-class optimization
> surface. Long-term progress will come not only from improving the downstream
> learner, but from learning which paths, time windows, reductions, and feature
> families should carry a database's task-relevant signal into that learner.
> Keeping this boundary explicit makes the learner pluggable: today CatBoost;
> tomorrow TabPFN-3 or another tabular or relational foundation model.

Kurve has pursued this direction from the outset: predictive performance
depends on adapting the relational representation as well as the learner that
consumes it. Recent independent results validate that longstanding bet from
adjacent directions. KumoRFM-2 reports that task-specific fine-tuning improves
its average SALT MRR from 0.83 in-context to 0.89, showing that even a strong
relational foundation model benefits materially from task adaptation
([KumoRFM-2](https://arxiv.org/abs/2604.12596)). Prior Labs' TabPFN-Rel couples
Deep Feature Synthesis with TabPFN-3 and obtains leading RelArena results,
demonstrating the value of giving a foundation learner a separately constructed
relational representation
([RelArena and TabPFN-Rel](https://arxiv.org/abs/2608.16319)). Neither system
optimizes exactly the same object as KurveRSC, but both reinforce KurveRSC's
original modular thesis: relational representation and downstream learning
should be adapted jointly without permanently binding either layer to the
other.

### How KurveRSC differs from Deep Feature Synthesis

**Deep Feature Synthesis generates a relational feature space; KurveRSC
selects a relational program by measuring how well its complete feature frame
works with the downstream learner.**

| Dimension | Deep Feature Synthesis | GraphReduce | KurveRSC |
|---|---|---|---|
| Primary object | Composed feature definitions | Executable table graph and node operations | Search over complete GraphReduce programs |
| Learner role | Normally fitted after synthesis | External to the execution engine | In the loop: AUROC or MAE scores every candidate frame |
| Task adaptation | Caller chooses primitives and depth; later feature selection can remove columns | Caller configures one graph program | Search jointly chooses depth, families, annotations, budgets, and temporal policy |
| Final artifact | Feature definitions and materialized table | Reduced frame and operation lineage | Selected configuration, frozen execution plan, schema, and fitted learner |
| Inference | Recompute the chosen definitions | Re-execute the configured graph | Replay the learned plan with feature discovery disabled |

Learner regularization over a wide DFS matrix can choose among columns that
were generated, but it cannot recover paths, time windows, feature families,
or propagation depths that were never materialized. KurveRSC makes those
upstream choices part of validation-guided selection while retaining a
replaceable downstream model. See the technical report's
[full comparison](https://github.com/kurveai/kurversc/blob/main/docs/kurversc-technical-report.md#difference-from-relational-feature-synthesis).

The [original KurveRSC technical report](https://github.com/kurveai/kurversc/blob/main/docs/kurversc-technical-report.md)
([PDF](https://github.com/kurveai/kurversc/blob/main/docs/kurversc-technical-report.pdf)) explains the
GraphReduce algorithm, relational feature families, learner-guided graph search,
point-in-time guarantees, frozen-plan lifecycle, and evaluation protocol.

## Complete staged pipeline (0.2.0)

The complete staged procedure is available through `TaskContext`,
`run_staged_task` and the `kurversc-staged` command. It includes graph search,
production-faithful finalist checks, temporal support/readout selection and
final refitting. Native `fit` / `predict` remain component interfaces.

See [the revised procedure and validation status](docs/staged-release.md).
Historical resource measurements describe the earlier recipe. Revised full-run
performance and resource requirements have not yet been established.

## Quickstart

The native graph-fitting API is one function. The historical performance tables
below use the superseded staged procedure. The current [staged procedure](docs/staged-release.md)
adds temporal policy and readout selection. For the native API, give `fit` an entity table, a
label table, their join keys, target, and authoritative train/validation split:

```bash
pip install "kurversc[relbench]"  # omit [relbench] for ordinary tables
```

```python
import kurversc

result = kurversc.fit(
    parent_node="customers.parquet",
    label_node="churn_labels.parquet",
    parent_key="customer_id",
    label_key="customer_id",
    target="churn",
    split_column="split",  # values: train / validation
)

print(result.best_config)         # highest validation ROC AUC or lowest MAE
print(result.recommended_config)  # simpler config when the gain is negligible
print(result.full_validation_score)
print(result.results)             # complete configuration-search audit trail
```

`result` is the fitted KurveRSC artifact: the selected `GraphConfig`, frozen
GraphReduce feature-operation plan, downstream learner, feature schema, and
validation metadata. Pass it to `kurversc.predict(...)` to replay the exact
learned relational program at new cutoff dates.

## Historical RelArena performance

These are the superseded October 27 author-run measurements. The current
accepted-component revision is undergoing fresh evaluation and has no revised
21-task Elo result. Earlier strict-cutoff compliance was not established.

The frozen complete staged procedure finished **all 21 official RelBench v1
tasks**, with **no failures or historical score substitutions**. The final
server scorer ran on **October 27, 2026**. The packaged versions are **KurveRSC
0.2.0 / GraphReduce 1.10.18**; the score-producing wheels used development
labels 0.1.1 / 1.10.17. Their Python payloads match before version stamping;
see the [source-parity receipt](docs/staged-source-parity.json).

These results use the complete `kurversc.run_staged_task` procedure, exposed in
RelArena as `kurversc-latest`. Native `fit` / `predict` alone run a narrower
procedure. See the [reproduction instructions](docs/staged-release.md) and
[technical report](docs/kurversc-staged-technical-report.md).

### Current per-task comparison

The comparator is **`tabpfn-rel-client-2026-09-28`**, captured with the other
RelArena results on October 25. Previous KurveRSC means its rows in that same
capture. It differs from the older September 2 README snapshot.

| Dataset / task | Metric | Previous KurveRSC | KurveRSC | TabPFN-Rel client | Winner |
| --- | --- | --- | --- | --- | --- |
| rel-amazon/item-churn | AUROC ↑ | 0.827256 | 0.829162 | 0.828653 | KurveRSC |
| rel-amazon/item-ltv | MAE ↓ | 42.273080 | 42.575857 | 45.270515 | KurveRSC |
| rel-amazon/user-churn | AUROC ↑ | 0.710347 | 0.711315 | 0.711698 | Client |
| rel-amazon/user-ltv | MAE ↓ | 14.020545 | 14.005336 | 13.877122 | Client |
| rel-avito/ad-ctr | MAE ↓ | 0.03344026 | 0.03281736 | 0.03125771 | Client |
| rel-avito/user-clicks | AUROC ↑ | 0.658406 | 0.647785 | 0.691631 | Client |
| rel-avito/user-visits | AUROC ↑ | 0.674721 | 0.673024 | 0.672453 | KurveRSC |
| rel-event/user-attendance | MAE ↓ | 0.25866836 | 0.23748723 | 0.24165168 | KurveRSC |
| rel-event/user-ignore | AUROC ↑ | 0.832628 | 0.843941 | 0.784037 | KurveRSC |
| rel-event/user-repeat | AUROC ↑ | 0.779679 | 0.806016 | 0.760495 | KurveRSC |
| rel-f1/driver-dnf | AUROC ↑ | 0.746484 | 0.740482 | 0.727809 | KurveRSC |
| rel-f1/driver-position | MAE ↓ | 3.863205 | 3.960206 | 3.757398 | Client |
| rel-f1/driver-top3 | AUROC ↑ | 0.664957 | 0.803766 | 0.791702 | KurveRSC |
| rel-hm/item-sales | MAE ↓ | 0.03216765 | 0.03173097 | 0.03647878 | KurveRSC |
| rel-hm/user-churn | AUROC ↑ | 0.696916 | 0.711248 | 0.706021 | KurveRSC |
| rel-stack/post-votes | MAE ↓ | 0.06324286 | 0.06273966 | 0.06273299 | Client |
| rel-stack/user-badge | AUROC ↑ | 0.877626 | 0.877817 | 0.883443 | Client |
| rel-stack/user-engagement | AUROC ↑ | 0.903027 | 0.907724 | 0.907662 | KurveRSC |
| rel-trial/site-success | MAE ↓ | 0.40384204 | 0.39135221 | 0.37761499 | Client |
| rel-trial/study-adverse | MAE ↓ | 41.270229 | 35.212072 | 34.401374 | Client |
| rel-trial/study-outcome | AUROC ↑ | 0.712131 | 0.755106 | 0.764556 | Client |

KurveRSC wins **11 of 21** direct comparisons with that client: **8 of 12
classification tasks** and **3 of 9 regression tasks**. Sixteen scores improve
over the captured previous KurveRSC and five decline. Winners use full precision;
post-votes remains a narrow loss. The [score CSV](docs/assets/kurversc-staged-scores-20261027.csv)
contains full precision and result/prediction hashes.

### Aggregate RelArena leaderboard

The official scorer replaces the captured old KurveRSC rows with these 21 fresh
scores, retaining the other 14 methods and the constant-global Elo anchor at
1000. KurveRSC ranks **first by point Elo: 1948.6 versus 1937.1** for the client.
The bootstrap intervals overlap. This is the completed author-run comparison
on a fixed panel, pending independent maintainer reproduction and public
leaderboard acceptance.

| Elo rank | Method | Kind | Elo | Bootstrap + / - | Mean rank | Panel win rate | Rescaled loss |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | KurveRSC | system | 1948.6 | +101.5 / -76.2 | 3.714 | 80.61% | 0.073390 |
| 2 | TabPFN-Rel client (2026-09-28) | model | 1937.1 | +105.8 / -67.5 | 3.857 | 79.59% | 0.073884 |
| 3 | TabPFN-Rel local (2026-09-28) | model | 1893.5 | +92.6 / -62.2 | 4.429 | 75.51% | 0.075685 |
| 4 | RT-J | system | 1839.7 | +145.5 / -118.9 | 5.190 | 70.07% | 0.153949 |
| 5 | RT-PluRel | system | 1792.1 | +106.5 / -96.8 | 5.905 | 64.97% | 0.138969 |
| 6 | TabPFN-Rel client (2026-08-15) | model | 1792.1 | +82.1 / -60.1 | 5.905 | 64.97% | 0.165097 |
| 7 | Nori-Rel | model | 1739.8 | +108.3 / -72.0 | 6.714 | 59.18% | 0.125534 |
| 8 | TabPFN-Rel local (2026-08-15) | model | 1697.3 | +77.6 / -83.1 | 7.381 | 54.42% | 0.205775 |
| 9 | GraphSAGE | model | 1648.3 | +85.2 / -108.6 | 8.143 | 48.98% | 0.223353 |
| 10 | RelGT | model | 1567.2 | +104.1 / -127.9 | 9.357 | 40.31% | 0.332101 |
| 11 | RDBLearn | model | 1525.0 | +79.2 / -95.1 | 9.952 | 36.05% | 0.290594 |
| 12 | RelGNN-ES | model | 1512.8 | +107.0 / -97.2 | 10.119 | 34.86% | 0.321048 |
| 13 | LightGBM | model | 1355.5 | +133.5 / -192.6 | 12.000 | 21.43% | 0.554574 |
| 14 | Constant/entity | model | 1263.7 | +138.6 / -179.8 | 12.857 | 15.31% | 0.619732 |
| 15 | Constant/global | model | 1000.0 | +79.5 / -165.3 | 14.476 | 3.74% | 0.944721 |

Panel win rate averages comparisons against all other methods; it is different
from the direct 11–10 client comparison. Earlier and later TabPFN versions are
separate participants in this captured panel.

#### Ranking by rescaled loss

Lower mean per-task min-max rescaled error is better. This metric is distinct
from regression nMAE normalized by the training-target standard deviation.

| Loss rank | Method | Rescaled loss | Elo rank | Elo |
| --- | --- | --- | --- | --- |
| 1 | KurveRSC | 0.073390 | 1 | 1948.6 |
| 2 | TabPFN-Rel client (2026-09-28) | 0.073884 | 2 | 1937.1 |
| 3 | TabPFN-Rel local (2026-09-28) | 0.075685 | 3 | 1893.5 |
| 4 | Nori-Rel | 0.125534 | 7 | 1739.8 |
| 5 | RT-PluRel | 0.138969 | 5 | 1792.1 |
| 6 | RT-J | 0.153949 | 4 | 1839.7 |
| 7 | TabPFN-Rel client (2026-08-15) | 0.165097 | 6 | 1792.1 |
| 8 | TabPFN-Rel local (2026-08-15) | 0.205775 | 8 | 1697.3 |
| 9 | GraphSAGE | 0.223353 | 9 | 1648.3 |
| 10 | RDBLearn | 0.290594 | 11 | 1525.0 |
| 11 | RelGNN-ES | 0.321048 | 12 | 1512.8 |
| 12 | RelGT | 0.332101 | 10 | 1567.2 |
| 13 | LightGBM | 0.554574 | 13 | 1355.5 |
| 14 | Constant/entity | 0.619732 | 14 | 1263.7 |
| 15 | Constant/global | 0.944721 | 15 | 1000.0 |

KurveRSC also ranks first by this panel's rescaled loss: **0.073390 versus
0.073884** for the client. Scoring-pool membership changes the ratings:

| Reporting scope | KurveRSC Elo | Client Elo | KurveRSC Elo rank |
|---|---:|---:|---:|
| Replace previous KurveRSC, 15 methods | 1948.6 | 1937.1 | 1 |
| Retain previous KurveRSC and add the dated staged system, 16 methods | 1954.2 | 1940.1 | 1 |

Both calculations use the same 21 fresh scores. The participant matrices below
and the main aggregate tables use the 15-method replacement panel. The
[retained-version scorer output](docs/assets/kurversc-staged-additional-version-panel-20261027.json)
records the separate 16-method calculation.

### Per-task results across all participants

Bold marks the best full-precision point score across all 15 methods, including
both dated client/local TabPFN versions. Matrix date suffixes refer to 2026.
The [complete score panel CSV](docs/assets/kurversc-staged-panel-20261027.csv)
contains all 315 method/task records.

<details>
<summary><strong>Classification AUROC higher is better</strong></summary>

| Dataset / task | KurveRSC | Client 09-28 | Local 09-28 | Client 08-15 | Local 08-15 | RT-J | RT-PluRel | Nori-Rel | GraphSAGE | RelGT | RDBLearn | RelGNN-ES | LightGBM | Constant/entity | Constant/global | Overall winner |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| rel-amazon/item-churn | 0.829162 | 0.828653 | 0.827685 | 0.827996 | 0.827857 | **0.832556** | 0.831014 | 0.822805 | 0.830527 | 0.823835 | 0.819538 | 0.785579 | 0.662211 | 0.728875 | 0.500000 | RT-J |
| rel-amazon/user-churn | 0.711315 | 0.711698 | 0.706670 | 0.708649 | 0.702403 | **0.714327** | 0.713592 | 0.690798 | 0.704596 | 0.701924 | 0.684375 | 0.694281 | 0.517056 | 0.634205 | 0.500000 | RT-J |
| rel-avito/user-clicks | 0.647785 | 0.691631 | **0.698621** | 0.675191 | 0.614522 | 0.671975 | 0.670345 | 0.636418 | 0.608674 | 0.644353 | 0.678769 | 0.667571 | 0.564163 | 0.504143 | 0.500000 | Local 09-28 |
| rel-avito/user-visits | **0.673024** | 0.672453 | 0.671587 | 0.668026 | 0.668811 | 0.667930 | 0.662691 | 0.662070 | 0.665758 | 0.662142 | 0.659624 | 0.648731 | 0.529282 | 0.602703 | 0.500000 | KurveRSC |
| rel-event/user-ignore | 0.843941 | 0.784037 | 0.817646 | **0.878659** | 0.701376 | 0.821496 | 0.823280 | 0.859148 | 0.758728 | 0.781507 | 0.664351 | 0.805393 | 0.777181 | 0.839930 | 0.500000 | Client 08-15 |
| rel-event/user-repeat | **0.806016** | 0.760495 | 0.760628 | 0.759291 | 0.769251 | 0.786598 | 0.777340 | 0.781217 | 0.784626 | 0.734358 | 0.744084 | 0.754612 | 0.748295 | 0.751805 | 0.500000 | KurveRSC |
| rel-f1/driver-dnf | **0.740482** | 0.727809 | 0.724794 | 0.732172 | 0.714468 | 0.588469 | 0.643268 | 0.715532 | 0.717235 | 0.711667 | 0.714551 | 0.726106 | 0.730298 | 0.699258 | 0.500000 | KurveRSC |
| rel-f1/driver-top3 | 0.803766 | 0.791702 | 0.810632 | 0.771426 | 0.792916 | 0.797757 | 0.750144 | 0.793922 | 0.725975 | **0.810841** | 0.780081 | 0.758864 | 0.738889 | 0.556530 | 0.500000 | RelGT |
| rel-hm/user-churn | **0.711248** | 0.706021 | 0.705416 | 0.705215 | 0.705690 | 0.706981 | 0.703784 | 0.694094 | 0.698525 | 0.689531 | 0.698352 | 0.682025 | 0.590081 | 0.647972 | 0.500000 | KurveRSC |
| rel-stack/user-badge | 0.877817 | 0.883443 | 0.884633 | 0.880386 | 0.863470 | **0.893610** | 0.893398 | 0.840720 | 0.888748 | 0.574286 | 0.771147 | 0.620584 | 0.537995 | 0.788956 | 0.500000 | RT-J |
| rel-stack/user-engagement | 0.907724 | 0.907662 | **0.907741** | 0.905994 | 0.905834 | 0.904240 | 0.895384 | 0.896160 | 0.905609 | 0.906731 | 0.858670 | 0.905054 | 0.811836 | 0.826717 | 0.500000 | Local 09-28 |
| rel-trial/study-outcome | 0.755106 | 0.764556 | 0.758230 | **0.764702** | 0.730607 | 0.711371 | 0.697735 | 0.742175 | 0.686232 | 0.668495 | 0.721205 | 0.657435 | 0.715018 | 0.500000 | 0.500000 | Client 08-15 |

</details>
<details>
<summary><strong>Regression MAE lower is better</strong></summary>

| Dataset / task | KurveRSC | Client 09-28 | Local 09-28 | Client 08-15 | Local 08-15 | RT-J | RT-PluRel | Nori-Rel | GraphSAGE | RelGT | RDBLearn | RelGNN-ES | LightGBM | Constant/entity | Constant/global | Overall winner |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| rel-amazon/item-ltv | 42.575857 | 45.270515 | 45.349612 | 46.768181 | 47.768328 | **42.562199** | 42.803588 | 43.708355 | 49.245337 | 48.673386 | 48.997527 | 52.458084 | 55.750750 | 65.351419 | 64.233613 | RT-J |
| rel-amazon/user-ltv | 14.005336 | 13.877122 | 13.885771 | 14.358212 | 14.400940 | **13.851486** | 13.879776 | 14.141218 | 14.415321 | 14.352129 | 14.577540 | 14.575225 | 16.784682 | 17.423469 | 16.782979 | RT-J |
| rel-avito/ad-ctr | 0.03281736 | 0.03125771 | 0.03135036 | **0.03107992** | 0.03137908 | 0.03459052 | 0.03643459 | 0.03176614 | 0.03896567 | 0.03649906 | 0.03410319 | 0.04264574 | 0.04124975 | 0.04120077 | 0.04306656 | Client 08-15 |
| rel-event/user-attendance | 0.23748723 | 0.24165168 | 0.23800486 | 0.24394361 | 0.23938282 | 0.24165984 | 0.24120715 | **0.23381999** | 0.24501802 | 0.26149320 | 0.24221516 | 0.24385787 | 0.26279776 | 0.26915220 | 0.26353422 | Nori-Rel |
| rel-f1/driver-position | 3.960206 | **3.757398** | 3.778471 | 3.769181 | 3.761699 | 3.791131 | 3.853460 | 3.884400 | 4.011112 | 4.765529 | 3.888649 | 4.265887 | 4.105829 | 4.103509 | 4.399101 | Client 09-28 |
| rel-hm/item-sales | **0.03173097** | 0.03647878 | 0.03653852 | 0.06050739 | 0.06136222 | 0.03734509 | 0.03930368 | 0.04380607 | 0.05515320 | 0.05316763 | 0.06713150 | 0.05649256 | 0.07529517 | 0.07803319 | 0.07608705 | KurveRSC |
| rel-stack/post-votes | 0.06273966 | **0.06273299** | 0.06284155 | 0.06788175 | 0.06795743 | 0.06804900 | 0.06412441 | 0.06504657 | 0.06489826 | 0.06790426 | 0.06771911 | 0.06790426 | 0.06609888 | 0.06937409 | 0.06790427 | Client 09-28 |
| rel-trial/site-success | 0.39135221 | 0.37761499 | 0.40421878 | 0.41262438 | 0.38575072 | 0.36228218 | 0.37127445 | 0.41773821 | **0.32485053** | 0.37010976 | 0.48583327 | 0.34049321 | 0.43750609 | 0.44114775 | 0.46222198 | GraphSAGE |
| rel-trial/study-adverse | 35.212072 | 34.401374 | 35.505899 | 39.753674 | 42.591708 | 37.492017 | 34.041422 | **30.883214** | 44.315261 | 44.125887 | 44.026705 | 46.270064 | 44.573495 | 57.533247 | 57.533247 | Nori-Rel |

</details>

### Runtime and resources

The complete tasks took **0.72–14.17 hours** on a CPU-only host with 495.77 GiB
RAM and 64 logical CPUs. The maximum task cgroup RAM peak was **333.51 GiB**
for user-badge. Every task completed below 24 hours. The
[resource report and CSV](docs/kurversc-staged-resources.md) contain every task's
runtime, peak RAM, CPU/storage coverage, SQL working-memory settings and
parallel-admission guidance for larger machines.

### Earlier results

The [September 2 README performance snapshot](docs/archive/relarena-performance-20260902.txt)
and [original technical report](docs/kurversc-technical-report.md) remain historical
references. They describe earlier pipelines and comparison panels. The
[current staged report](docs/kurversc-staged-technical-report.md) documents the
new implementation and final reproduction.

## Native API details

Both node arguments accept a pandas DataFrame, CSV/Parquet path, or the name of
a table/view on a supplied DuckDB connection. For explicit metadata, use
`Table` and `Labels`:

```python
result = kurversc.fit(
    parent_node=kurversc.Table(
        "users", name="users", key="Id", date="CreationDate"
    ),
    label_node=kurversc.Labels(
        "user_labels",
        key="user_id",
        target="will_return",
        timestamp="timestamp",
        split="split",
    ),
    tables=[
        kurversc.Table(
            "posts", name="posts", key="Id", date="CreationDate"
        ),
        kurversc.Table(
            "comments", name="comments", key="Id", date="CreationDate"
        ),
    ],
    relationships=[
        kurversc.Relationship(
            parent="users",
            child="posts",
            parent_key="Id",
            child_key="OwnerUserId",
        ),
        kurversc.Relationship(
            parent="posts",
            child="comments",
            parent_key="Id",
            child_key="PostId",
        ),
    ],
    connection=duckdb_connection,
)
```

When the target is a future aggregation over one of the graph's event tables,
let GraphReduce generate it natively instead of supplying a materialized label
table:

```python
label_node=kurversc.GraphLabels(
    table="orders",
    field="id",
    operation="bool",
    period_days=365,
    train_cutoffs=("2023-01-01", "2024-01-01"),
    validation_cutoffs=("2025-01-01",),
    test_cutoffs=("2026-01-01",),
    target="will_order",
)
```

This executes GraphReduce's `prep_for_labels()` and automatic `do_labels`
aggregation at every cutoff. `Labels` remains the correct interface for
authoritative external targets such as official RelBench task tables.

Relationships are required when the compute graph contains feature tables:
file names alone cannot determine foreign-key direction or whether a join is
one-to-many. The two label/entity keys are also explicit so label attachment is
never guessed.

<details>
<summary><strong>What <code>fit</code> searches</strong></summary>

The default search is deterministic and starts with the smallest base-only
configuration. Before building a graph, KurveRSC profiles a small sample from
every node and utility-ranks its source columns. Structural keys and cutoff
dates are always retained. A cap therefore admits the strongest observed
source columns instead of whichever columns happen to occur first in the
physical schema.

```python
result = kurversc.fit(
    ...,
    feature_family_max_columns=4,           # fixed columns per family
    feature_family_max_features_per_column=32,
    feature_propagation_max_functions_per_column=1,
    feature_ranking_rows=2_000,
    forward_search_beam_width=2,
    screening_rows=10_000,
    sample_rows=50_000,                     # confirmation fidelity
    confirmation_top_k=8,                  # diverse 50K candidates
    rerank_top_k=3,                         # full-data finalists
    rerank_cutoff_frames=3,                 # sequential walk-forward folds
    adaptive_depth_promotion=True,
    capability_pruning=True,
    search_max_features=8_000,
    random_state=42,                        # CatBoost and sampling seed
)
```

`random_state` is a reproducibility seed, not a trial count. KurveRSC uses the
same deterministic seed for competing graph configurations so stochastic model
behavior does not favor one shape over another.

The family lattice contains independent additions of `temporal`, `sequence`,
`conditional`, and `episode` to `base`, including their combinations. It does
not require a weak family to be present before a later family can be tested.
Depth 3 is limited to combinations of `base`, `temporal`, and `sequence`; wider
conditional and episode programs use depths 1 and 2.

At the default four-column budget, the forward beam executes at most 24 graph
shapes: up to four adaptive base variants, then at most 8, 6, 4, and 2
survivors across the successive family levels. The complete 72-shape lattice remains in the audit
trail with non-executed candidates marked `pruned`. A wider budget is opt-in:
`feature_family_max_column_options=(4, 8)` adds another 72 potential records,
but only the raw narrow-budget winner and the complexity-aware narrow-budget
recommendation are promoted from four to eight source columns (with the next
score-ranked shape filling the second slot when they are identical). That
expanded funnel normally materializes at most 28 configurations rather than
exhaustively running all 144 potential combinations.

```text
default cap:       4 base + 8 singles + 6 pairs + 4 triples + 2 quadruples = 24
optional wide cap: top-2 complete narrow-cap shapes                          =  2
                                                                           ----
maximum materialized by the opt-in expanded funnel                          = 26
```

The default search is multi-fidelity. Beam-admitted configurations are first
screened with at most 10,000 rows per node. Eight structurally diverse
candidates are rebuilt and rescored with `sample_rows`: the raw and
complexity-aware leaders plus representatives of available families, deeper
propagation, and both annotation policies. The strongest three confirmed
shapes are then reranked over three complete relational cutoff folds before the
final graph program is selected. `result.results`,
`result.confirmation_results`, and `result.rerank_results` expose the three
audit trails separately.

Adaptive depth promotion evaluates both annotation policies at depth 1,
promotes only the stronger policy to depth 2, and admits depth 3 only when the
depth-2 gain exceeds both the task tolerance and validation uncertainty.
Capability pruning removes families that cannot produce operations for the
available graph schema. Finally, `search_max_features` uses the source-column
audit and observed parent widths to reject a predicted feature explosion before
its SQL is materialized. All three guards can be disabled independently.

Customize the stages with `max_depth`, `auto_annotate_options`, and
`feature_family_stages`, or pass explicit `graph_configs` to override the
frontier. Set `feature_family_max_column_options=(4, 8)` to opt into wider
refinement, or include `None` as a tier to test an uncapped finalist.
`feature_family_max_features_per_column` is a separate GraphReduce guardrail:
it prevents a single temporal or categorical source from expanding into an
unbounded number of derived SQL features. The propagation cap prevents each
already-derived column from branching again at every graph hop while retaining
its canonical continuation (`max→max`, `min→min`, `sum→sum`, `count→sum`, and
`avg→avg`). Inspect `result.feature_audit` to see every source column's role,
utility score, family rank, eligible budget tiers, and exclusion reason.

`semantic` uses automatic annotations when `auto_annotate_features=True` (or
caller-supplied GraphReduce annotations). `context` requires peer-group keys;
`Table.context_keys` supplies them directly, and the RelBench adapter derives
them from foreign keys other than the edge currently being reduced.

Every candidate holds the remaining node policy fixed: GraphReduce's native
1/3/4/7/14/30/60/90/180/365/730-day time-series periods (plus the compute
horizon when it exceeds 365 days) unless `infer_ts_periods=True`, categorical
cardinality threshold 20, categorical top-k 5, automatic text features disabled, and annotation
bounds 10 categorical columns, 4 gated numeric columns, and top-k 3. These settings are assigned to
each node explicitly so they are effective with GraphReduce 1.10.

### Optional TabPFN v3 estimator

CatBoost remains the default downstream estimator. Install the local TabPFN
integration and select v3 explicitly with:

```bash
pip install "kurversc[relbench,tabpfn]"
```

```python
result = kurversc.fit(
    **problem.fit_kwargs(),
    model_backend="tabpfn_v3",
    estimator_train_rows=10_000,
    model_params={
        "n_estimators": 2,
        "fit_mode": "low_memory",
    },
)
```

`estimator_train_rows` is applied consistently to sampled configuration
screening, full-history finalist fitting, and final train-plus-validation
fitting. Classification samples are stratified and deterministic. When the
TabPFN backend is selected without an explicit cap, KurveRSC defaults it to
10,000 rows. Graph materialization remains independent of this estimator-only
cap, and the fitted artifact can be replayed with `kurversc.predict(...)`.

By default, each search source—including labels—is exposed to GraphReduce
through a temporary DuckDB view capped at `sample_rows`. Set
`search_full_data=True` to evaluate every candidate against complete source
tables instead:

```python
result = kurversc.fit(
    parent_node=parent,
    label_node=labels,
    tables=tables,
    relationships=relationships,
    sample_rows=50_000,       # still used for ordinary sampled searches
    search_full_data=True,    # disables row sampling during config search
    full_training_frames=3,   # cutoff dates used for final frame ensembling
    infer_ts_periods=True,
    auto_text_features=False,
)
```

`search_full_data=True` uses complete rows at every eligible search cutoff
selected by `search_training_frames` for every configuration admitted by the
forward funnel. The winning
configuration is selected
directly from those validation scores unless temporal reranking is enabled,
and is then fit across the requested full-training cutoff dates. Adapters can
attach a separate connected `search_source` while retaining their uncapped
production source. A
new graph is created for every candidate because GraphReduce execution mutates
node state. If labels contain a timestamp, features are built at each label
cutoff; otherwise labels are split randomly (or by `split_column`) and the
current time is used as the feature cutoff.

Classification candidates use CatBoost and validation ROC AUC. Regression
candidates use CatBoost and validation MAE. The highest-performing candidate
is always retained as `best_trial`. KurveRSC also records feature count,
feature/model time, and an estimated validation-metric standard error. Trials
that add at least 2x as many features without improving beyond both the fixed
0.002 AUC / 0.5% relative MAE floor and the configured uncertainty threshold
are marked in `result.complexity_notes`. `recommended_trial` is the
lowest-feature candidate statistically indistinguishable from the raw winner;
`best_trial` remains the unpenalized validation winner. Set
`complexity_uncertainty_multiplier=0` to use only the fixed tolerances.

By default, rerank the three strongest confirmed finalists over three
walk-forward full-data cutoff folds:

```python
result = kurversc.fit(
    ...,
    rerank_top_k=3,
    rerank_cutoff_frames=3,
    rerank_stability_penalty=0.25,
)
```

The reranker learns and scores one cutoff frame at a time, releases it, and
then advances to the next fold. Classification maximizes mean ROC AUC minus
the configured standard-deviation penalty; regression minimizes mean MAE plus
that penalty. The raw stability-adjusted winner is selected; the complexity
guard remains a screening and audit mechanism but cannot override this
full-frame evidence. The audit trail is available as `result.rerank_results`.
Set `rerank_cutoff_frames=1` for a single full-data train-to-validation rerank;
the stability penalty is then zero because there is only one score.

</details>

<details>
<summary><strong>What the returned fitted model means</strong></summary>

`fit` has a nine-stage lifecycle:

1. Utility-rank source columns and record the capped feature-funnel audit.
2. Build beam-admitted candidates from sampled source views, or from complete
   source rows when `search_full_data=True`.
3. Rank candidates by one-frame validation ROC AUC or MAE and promote only the
   strongest graph shapes to broader source-column budgets.
4. Confirm a structurally diverse bounded candidate set at medium fidelity.
5. Rerank the three strongest confirmed candidates over sequential full-data
   walk-forward cutoff folds and select the raw stability-adjusted winner.
6. Freeze its exact GraphReduce operation plan and training-only feature schema.
7. Materialize one production cutoff at a time and fit an independent CatBoost
   model for that frame.
8. Score validation with the training-frame ensemble, then add independently
   fitted validation-frame models to the final train-plus-validation ensemble.
9. Replay the plan with `GraphReduce(train=False)` at test cutoffs and expose
   predictions as `result.test_predictions`. If an external `Labels` test
   split contains targets, KurveRSC also records a test score.

The resulting production artifact is `result.fitted_model`: selected
`GraphConfig`, frozen execution plan, ordered feature schema, CatBoost model,
and validation/test metadata. `result.model` returns its final CatBoost model;
`result.execution_plan` returns the production GraphReduce plan. Validation
and test never run feature inference or annotation again.

When `infer_ts_periods=True`, KurveRSC asks GraphReduce to infer
relationship-specific event-cadence windows. Each dated node or relationship
can then replace the initial `[7, 30, 90]` windows with compact, data-derived
lookbacks spanning the configured compute horizon. KurveRSC stores those
inferred periods inside the frozen execution plan and restores them during
validation, outer refit, and prediction; replay never re-infers them.

Replay the fitted artifact on another timestamped entity frame with the same
declarative graph metadata:

```python
predictions = kurversc.predict(
    result,
    parent_node=parent,
    prediction_node=kurversc.Labels(
        scoring_rows, key="customer_id", timestamp="timestamp"
    ),
    tables=tables,
    relationships=relationships,
)
```

The output preserves prediction-row order and adds a `prediction` column.

Point-in-time production training can use many frames. By default, every
configuration is screened on one frame at the latest eligible cutoff. With
`search_full_data=True`, that frame uses all available rows. Supply all valid
cutoffs through `GraphLabels.train_cutoffs`, or all timestamped rows through
`Labels`, then choose the incremental production frame count:

```python
result = kurversc.fit(
    ...,
    search_full_data=True,     # evaluate all candidates on complete rows
    full_training_frames=3,    # 3 evenly spaced available train cutoffs
)
```

`full_training_frames=None` (the default) uses every available training
cutoff. These are point-in-time graph frames, not partitions of raw event
tables: every frame sees the complete history allowed by its cutoff, and all
frames replay the selected operation plan. KurveRSC releases each materialized
feature frame before constructing the next one. Independent CatBoost models are
combined as an ensemble, so the final fit never concatenates those wide frames
in memory. When
`full_training_frames=1`, KurveRSC always uses the latest eligible training
cutoff. The search audit trail is available as `result.results`.

</details>

<details>
<summary><strong>Official RelBench tasks</strong></summary>

`load_relbench_problem` uses the production RelBench dataset, task tables,
date keys, primary keys, and foreign keys without adding task-specific feature
expressions:

```python
import kurversc

problem = kurversc.load_relbench_problem(
    "rel-stack",
    "user-badge",
    sample_rows=10_000,
    max_train_timestamps=1,
    max_enrichment_columns=8,
)
result = kurversc.fit(**problem.fit_kwargs(), sample_rows=10_000)
```

For a full-data configuration search followed by a three-cutoff production
fit, use:

```python
problem = kurversc.load_relbench_problem(
    "rel-stack",
    "user-badge",
    sample_rows=50_000,
    search_full_data=True,
    max_train_timestamps=3,
)
result = kurversc.fit(
    **problem.fit_kwargs(),
    sample_rows=50_000,
    search_full_data=True,
    full_training_frames=3,
    infer_ts_periods=True,
    auto_text_features=False,
)
```

This runs the beam-admitted graph configurations on complete rows at the latest
training cutoff, promotes only the strongest shapes to the wider source-column
budget, and fits the selected configuration across three cutoff dates while
retaining only one materialized feature frame.

Install the optional adapter with `pip install "kurversc[relbench]"`. The object
adapter `relbench_problem_from_objects(...)` accepts a task, an already-censored
RelBench database, and its train/validation tables; RelArena uses this path so
its official inner and outer database cutoffs remain authoritative.

Relational schemas do require keys. This adapter reads them from official
RelBench metadata; for ordinary files or database tables, provide them with
`Table` and `Relationship`. Self-referential/cyclic foreign keys are omitted
because GraphReduce currently uses an acyclic `DiGraph`; every reachable
acyclic foreign-key path is represented as its own node instance. Referenced
dimension tables reached through association/event tables are joined with
`reduce=False`; by default their projected feature attributes are capped at
eight, excluding high-cardinality free text. Pass
`max_enrichment_columns=None` to retain every dimension attribute.

For temporally meaningful relational evaluation, provide `Labels.timestamp`
and date columns on event tables. A dated parent is always filtered with
`parent.date <= Labels.timestamp` before feature inference and again through
GraphReduce's `do_filters_ops`. If the parent is a genuinely timeless entity
table, declare `Table(..., timeless=True)` explicitly; an omitted parent date
is otherwise rejected for temporal labels. Without event dates, KurveRSC
cannot distinguish historical features from future data.

</details>

<details>
<summary><strong>Local customer example</strong></summary>

[`examples/cust_data_future_order.py`](examples/cust_data_future_order.py)
contains a complete run for `/usr/local/lake/cust_data`. It derives train and
validation labels through GraphReduce for “places an order in the following
365 days,” declares
the customer root as explicitly timeless, supplies every primary/foreign key
and event date, and runs the default beam-pruned configuration funnel. The example enables
the `kurversc` logger at `INFO`, showing every attempted configuration, its
score/feature count/timing, and the selected configuration.

</details>

## Citation

If you use KurveRSC in research, please cite the
[KurveRSC technical report](docs/kurversc-technical-report.pdf):

```bibtex
@techreport{madrigal2026kurversc,
  title       = {KurveRSC: Validation-Guided Relational Signal Compression with a Downstream Learner in the Loop},
  author      = {Madrigal, Wes},
  institution = {Kurve AI},
  year        = {2026},
  month       = sep,
  url         = {https://github.com/kurveai/kurversc/blob/main/docs/kurversc-technical-report.pdf}
}
```
