Text Classification
PEFT
Safetensors
forecasting
calibration
prediction-markets
probability-estimation
scalar-head
Eval Results (legacy)
Instructions to use Laplace-AI-Research/tarot-draw-1-7b with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use Laplace-AI-Research/tarot-draw-1-7b with PEFT:
from peft import PeftModel from transformers import AutoModelForSequenceClassification base_model = AutoModelForSequenceClassification.from_pretrained("Qwen/Qwen3-1.7B") model = PeftModel.from_pretrained(base_model, "Laplace-AI-Research/tarot-draw-1-7b") - Notebooks
- Google Colab
- Kaggle
Rebuild the Kalshi transfer set; correct a contamination claim that was wrong. The old set had no builder and its prompts depended on an API field present in no archive we kept. Elections is worse than a constant on BOTH sides of the freeze, so the old 'contamination-free subset' framing attributed to contamination what was category composition.
269de9b verified | """Build the Kalshi transfer evaluation set from a rule that is written down. | |
| uv run python scripts/kalshi_eval_set.py --out data/kalshi_score_778_rebuilt.json | |
| ## Why this exists | |
| `data/kalshi_score_778.json` has no builder anywhere in this repository. Its | |
| metadata states a filter -- *"judgment categories only; numeric/threshold titles | |
| removed; lifetime>=7d; one market per event"* -- but no committed code implements | |
| it, so the population behind every Kalshi transfer number on three public model | |
| cards cannot be rebuilt or checked. §9.1 of the technical report discloses this; | |
| this script is the promise being kept. | |
| `polymarket_eval_set.py` is the precedent, including its discipline: **the rule | |
| below was written before this was run and was not adjusted afterwards.** No | |
| stage of it was chosen by looking at what it does to the row count or to any | |
| model comparison. | |
| ## The rule, stated in full | |
| The artifact's four clauses, each made executable, plus two the artifact's own | |
| `filter` string omits and which are needed for the rule to determine a set at | |
| all. Those two are marked ADDED. | |
| 1. **Judgment categories only.** A judgment question resolves on a human or | |
| institutional decision, or on a world event a forecaster reasons about. It is | |
| NOT a price or index level, a sporting result, a physical measurement, or a | |
| mechanical count of utterances. On Kalshi's own 18-category series taxonomy | |
| that admits: Politics, Elections, Economics, Science and Technology, World, | |
| Companies, Health, Social, Transportation, Education, Entertainment, Exotics. | |
| It excludes: Sports, Financials, Crypto, Commodities, Climate and Weather, | |
| Mentions. | |
| Entertainment is admitted on principle -- an award outcome is a judgment -- | |
| and its numeric titles are removed by clause 2 rather than by category. | |
| 2. **Numeric / threshold titles removed.** Any title expressing a threshold on a | |
| continuous quantity. Matched on the title, not the category, because a | |
| judgment category still carries "above $X" markets. | |
| 3. **Lifetime >= 7 days**, measured `settlement_ts - open_time`. `docs/11`'s | |
| domain prior: time to resolution is the property that has mattered twice, and | |
| a market open for under a week is a nowcast. | |
| 4. **One market per event.** Deterministic tiebreak: the market with the LONGEST | |
| lifetime, ties broken by ticker ascending. Stated because "one per event" | |
| alone does not say which one, and any unstated tiebreak is a silent | |
| researcher degree of freedom. | |
| 5. **ADDED -- multivariate combo markets excluded.** `docs/10` records that | |
| `/markets` has no default for `mve_filter`, so unfiltered pulls return | |
| multi-leg parlays whose titles are the legs comma-joined and whose timestamps | |
| track generation rather than events. They are not forecasting questions. The | |
| artifact's filter string does not mention them; without excluding them the | |
| rule admits 27M rows of noise. | |
| 6. **ADDED -- a resolution window.** The artifact's filter string names none, yet | |
| the shipped set spans 2026-06-20 to 2026-08-27, so a window was applied and | |
| not recorded. `--resolved-from` / `--resolved-to` make it explicit and | |
| default to the shipped set's own span so the comparison is like-for-like. | |
| **Clauses 5 and 6 are the finding, not an implementation detail.** The stated | |
| filter under-determines the set: two choices that materially change the | |
| population were never written down. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import gzip | |
| import hashlib | |
| import json | |
| import re | |
| import sys | |
| from collections.abc import Sequence | |
| from datetime import datetime | |
| JUDGMENT = { | |
| "Politics", "Elections", "Economics", "Science and Technology", "World", | |
| "Companies", "Health", "Social", "Transportation", "Education", | |
| "Entertainment", "Exotics", | |
| } | |
| MECHANICAL = { | |
| "Sports", "Financials", "Crypto", "Commodities", "Climate and Weather", | |
| "Mentions", | |
| } | |
| # Clause 2. Thresholds on a continuous quantity, however phrased. | |
| NUMERIC_TITLE = re.compile( | |
| r"(above|below|over|under|at least|at most|more than|less than|greater than|" | |
| r"exceed|reach|between)\s+[\$€£]?[\d,]+(\.\d+)?" | |
| r"|[\$€£][\d,]+(\.\d+)?" | |
| r"|\b\d+(\.\d+)?\s*(%|percent|bps|basis points)" | |
| r"|\bhow many\b|\bnumber of\b", | |
| re.IGNORECASE, | |
| ) | |
| def parse_ts(s: str | None) -> datetime | None: | |
| if not s: | |
| return None | |
| try: | |
| return datetime.fromisoformat(s.replace("Z", "+00:00")) | |
| except ValueError: | |
| return None | |
| def series_of(event_ticker: str) -> str: | |
| """Kalshi event tickers are SERIES-SUFFIX. The series is the part before the | |
| first hyphen; a ticker with no hyphen is its own series.""" | |
| return event_ticker.split("-", 1)[0] if event_ticker else "" | |
| def main(argv: Sequence[str] | None = None) -> int: | |
| ap = argparse.ArgumentParser(description=__doc__) | |
| ap.add_argument("--sweep", default="data/kalshi/sweep-20260824.jsonl.gz") | |
| ap.add_argument("--categories", default="data/kalshi/series_category.json") | |
| ap.add_argument("--out", default="data/kalshi_score_778_rebuilt.json") | |
| ap.add_argument("--resolved-from", default="2026-06-20") | |
| ap.add_argument("--resolved-to", default="2026-08-28") | |
| ap.add_argument("--min-lifetime-days", type=int, default=7) | |
| ap.add_argument("--compare", default="data/kalshi_score_778.json") | |
| args = ap.parse_args(argv) | |
| cats: dict[str, str] = json.load(open(args.categories, encoding="utf-8")) | |
| lo = parse_ts(args.resolved_from + "T00:00:00+00:00") | |
| hi = parse_ts(args.resolved_to + "T00:00:00+00:00") | |
| drop = { | |
| "not_finalized": 0, "mve": 0, "no_event": 0, "unknown_series": 0, | |
| "mechanical_category": 0, "numeric_title": 0, "outside_window": 0, | |
| "short_lifetime": 0, "bad_timestamps": 0, "no_result": 0, | |
| } | |
| best: dict[str, dict] = {} | |
| seen = 0 | |
| op = gzip.open if args.sweep.endswith(".gz") else open | |
| with op(args.sweep, "rt", encoding="utf-8") as h: | |
| for line in h: | |
| if not line.strip(): | |
| continue | |
| seen += 1 | |
| r = json.loads(line) | |
| if r.get("status") != "finalized": | |
| drop["not_finalized"] += 1; continue | |
| if r.get("mve_collection_ticker"): | |
| drop["mve"] += 1; continue | |
| ev = r.get("event_ticker") or "" | |
| if not ev: | |
| drop["no_event"] += 1; continue | |
| cat = cats.get(series_of(ev)) | |
| if cat is None: | |
| drop["unknown_series"] += 1; continue | |
| if cat in MECHANICAL or cat not in JUDGMENT: | |
| drop["mechanical_category"] += 1; continue | |
| title = r.get("title") or "" | |
| if NUMERIC_TITLE.search(title): | |
| drop["numeric_title"] += 1; continue | |
| res = r.get("result") | |
| if res not in ("yes", "no"): | |
| drop["no_result"] += 1; continue | |
| opened = parse_ts(r.get("open_time")) | |
| settled = parse_ts(r.get("settlement_ts")) | |
| if opened is None or settled is None: | |
| drop["bad_timestamps"] += 1; continue | |
| if not (lo <= settled < hi): | |
| drop["outside_window"] += 1; continue | |
| life = (settled - opened).days | |
| if life < args.min_lifetime_days: | |
| drop["short_lifetime"] += 1; continue | |
| row = { | |
| # keyed on the MARKET ticker, not the event ticker: the shipped | |
| # set uses kalshi:KXNEWGLENN-262-JUL where the event is | |
| # KXNEWGLENN-262. Keying on the event made 642 genuine matches | |
| # look like 30 and nearly produced a wrong finding about a | |
| # public artifact. | |
| "question_id": f"kalshi:{r.get('ticker') or ev}", | |
| "text": title, | |
| "open_date": opened.isoformat(), | |
| "scheduled_resolve_date": (parse_ts(r.get("close_time")) or settled).isoformat(), | |
| "resolved_at": settled.isoformat(), | |
| "outcome": 1 if res == "yes" else 0, | |
| "quarter": 0, | |
| "event_ticker": ev, | |
| "category": cat, | |
| "_lifetime": life, | |
| "_ticker": r.get("ticker") or "", | |
| } | |
| # clause 4: longest lifetime wins, ties by ticker ascending | |
| cur = best.get(ev) | |
| if cur is None or (row["_lifetime"] > cur["_lifetime"]) or ( | |
| row["_lifetime"] == cur["_lifetime"] and row["_ticker"] < cur["_ticker"] | |
| ): | |
| best[ev] = row | |
| rows = sorted(best.values(), key=lambda r: r["question_id"]) | |
| for r in rows: | |
| r.pop("_lifetime", None); r.pop("_ticker", None) | |
| print(f"scanned {seen:,} sweep rows") | |
| print("dropped:") | |
| for k, v in sorted(drop.items(), key=lambda kv: -kv[1]): | |
| print(f" {v:>10,} {k}") | |
| print(f"\nkept {len(rows):,} rows, one per event") | |
| payload = { | |
| "content_hash": hashlib.sha256( | |
| json.dumps([r["question_id"] for r in rows], sort_keys=True).encode() | |
| ).hexdigest(), | |
| "n": len(rows), | |
| "split": "transfer_test", | |
| "platform": "kalshi", | |
| "builder": "scripts/kalshi_eval_set.py", | |
| "filter": ("judgment categories only; numeric/threshold titles removed; " | |
| f"lifetime>={args.min_lifetime_days}d; one market per event " | |
| "(longest lifetime, ties by ticker); MVE combos excluded; " | |
| f"resolved in [{args.resolved_from}, {args.resolved_to})"), | |
| "judgment_categories": sorted(JUDGMENT), | |
| "mechanical_categories": sorted(MECHANICAL), | |
| "dropped": drop, | |
| "questions": rows, | |
| } | |
| with open(args.out, "w", encoding="utf-8") as h: | |
| json.dump(payload, h, indent=1, sort_keys=True) | |
| print(f"wrote {args.out}") | |
| if args.compare: | |
| try: | |
| old = json.load(open(args.compare, encoding="utf-8")) | |
| except FileNotFoundError: | |
| return 0 | |
| a = {q["question_id"] for q in old["questions"]} | |
| b = {r["question_id"] for r in rows} | |
| print(f"\n== against the shipped set ({args.compare}) ==") | |
| print(f" shipped {len(a):,} rebuilt {len(b):,} overlap {len(a & b):,}") | |
| print(f" in shipped only: {len(a - b):,}") | |
| print(f" in rebuilt only: {len(b - a):,}") | |
| if a == b: | |
| print(" IDENTICAL -- the stated rule reproduces the shipped set.") | |
| else: | |
| print(" NOT identical. The stated filter does not determine the set;") | |
| print(" see clauses 5 and 6 in this script's docstring.") | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |