Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/ts-d22-flip-cancelled.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"mostlyright": patch
---

Cancel the D-22 label-default flip, mirroring Python 34-06: `dataset()` with an
omitted `label` keeps the explicit, contractually-stable `label="cli"` default
silently — the one-time FutureWarning and the promised flip to `label=null` at
2.0 are removed. `_resetDefaultLabelWarning` remains importable as a
`@deprecated` no-op through 1.x (removed at 2.0).
12 changes: 7 additions & 5 deletions packages-ts/meta/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ export { core, markets, weather };
// leakage-safe composed training table; `research` is retained as a fully
// working alias.
//
// Phase 32 32-05 (D-22): the two are now DISTINCT wrappers over one shared body
// (ALIAS SPLIT) — `dataset()` fires a one-time FutureWarning when called without
// an explicit `label`; `research()` never does. This is the ONLY behavioral
// divergence; both carry the identical public signature. Removal of the split
// (and the implicit-cli default) lands at 2.0.
// Phase 32 32-05 (D-22): the two are DISTINCT wrappers over one shared body
// (ALIAS SPLIT). The D-22 default-flip FutureWarning was CANCELLED in 1.17
// (Python 34-06 parity) — the wrappers behave identically; the implicit
// `label="cli"` default is explicit + contractually stable. `research()`
// retires at 2.0.
export {
dataset,
research,
// @deprecated no-op stub (the D-22 latch it reset was removed in 1.17);
// kept importable through 1.x, removed at 2.0.
_resetDefaultLabelWarning,
type ResearchOptions,
type PairsRow,
Expand Down
8 changes: 4 additions & 4 deletions packages-ts/meta/src/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
// "daily_extremes" → the WU/NOAA-WRH extremes Polymarket resolves on;
// ADDS daily_extremes_* columns (native names, D-26).
// "none" → features-only; DROPS cli_high_f/cli_low_f/
// cli_report_type (the D-22 2.0 default).
// cli_report_type (the stable implicit default).
// BYO row-array → settlement-calendar aligner (all six sharp edges).
//
// - D-22 bridge: omitted-label `dataset()` returns cli_* bytes + a ONE-TIME
// FutureWarning; `research()` omitted-label returns cli_* bytes and NEVER
// - Omitted-label `dataset()`/`research()` return cli_* bytes silently (the
// D-22 flip + warning were cancelled in 1.17, Python 34-06 parity); never
// warns. See research.ts for the alias split that carries the divergence.
//
// The BYO aligner is adapted to TS conventions: Python takes a DataFrame, TS
Expand Down Expand Up @@ -207,7 +207,7 @@ export function validateLabel(label: LabelValue): void {
// ---------------------------------------------------------------------------

/**
* The `cli_*` columns dropped by `label="none"` (the D-22 2.0 features-only
* The `cli_*` columns dropped by `label="none"` (the features-only
* default). Mirrors Python "label=None DROPS cli_high_f/cli_low_f/
* cli_report_type".
*/
Expand Down
15 changes: 4 additions & 11 deletions packages-ts/meta/src/panels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import type { ByoLabelRow } from "./labels.js";
import { LabelAlignmentError } from "./labels.js";
import { type ResearchOptions, fireDefaultLabelWarningOnce, research } from "./research.js";
import { type ResearchOptions, research } from "./research.js";

/** A single panel row — a per-station `dataset()` row with `station` first. */
export type PanelRow = Record<string, unknown>;
Expand Down Expand Up @@ -122,16 +122,9 @@ export async function datasetPanel(
}
}

// FIX-5 (D-22): a panel called WITHOUT an explicit label (and no BYO
// panelLabels) fires the bridge FutureWarning ONCE for the whole panel —
// mirroring Python `_compose_panel`. The per-station fan-out goes through
// `research()` (which never warns), so this is exactly-once, never
// N-per-station. A panel WITH an explicit label warns zero.
const panelLabelOmitted =
panelLabels === undefined && (singleOptsBase as { label?: unknown }).label === undefined;
if (panelLabelOmitted) {
fireDefaultLabelWarningOnce();
}
// D-22 flip CANCELLED (34-06 parity): an omitted-label panel keeps the
// explicit, contractually-stable `label="cli"` default silently — no
// bridge warning fires anywhere anymore.

const allRows: PanelRow[] = [];
const provStations: Record<string, StationProvenance> = {};
Expand Down
107 changes: 46 additions & 61 deletions packages-ts/meta/src/research.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,10 @@ export interface ResearchOptions extends ResearchKwargsExtension {
// (features-only == `"none"`), or a BYO label row-array through the
// settlement-calendar aligner.
//
// D-22 bridge: OMITTED `label` on `dataset()` returns `cli_*` bytes + a
// ONE-TIME FutureWarning; OMITTED `label` on `research()` returns `cli_*`
// bytes and NEVER warns (the alias split carries the divergence). An
// explicit `label` on either entry is honored verbatim and never triggers
// the bridge warning — so pinned parity calls stay warning-free.
// OMITTED `label` on either entry returns `cli_*` bytes silently — the
// explicit, contractually-stable default (the D-22 flip + its FutureWarning
// were CANCELLED in 1.17, mirroring Python 34-06). An explicit `label` is
// honored verbatim.
//
// `"cli"` is IDENTITY (the `cli_*` columns are already on the frame from
// buildPairs) → byte-stable parity by construction.
Expand All @@ -226,28 +225,27 @@ export interface ResearchOptions extends ResearchKwargsExtension {
labels_as_known?: LabelsAsKnown;
}

// ── Phase 32 32-05 (D-22) — one-time FutureWarning latch (fs.ts pattern) ──
// ── D-22 flip CANCELLED (Python 34-06 parity) ─────────────────────────────
//
// `dataset()` called WITHOUT an explicit `label` fires this ONCE (test-resettable
// via `_resetDefaultLabelWarning`); `research()` never fires it. Mirrors the
// Python `_D22_BRIDGE_WARNING` + the fs.ts house latch (`console.warn` boolean
// + a `_reset*()` test hook).
let _defaultLabelWarned = false;
// The D-22 bridge (a one-time FutureWarning promising the omitted-label
// default would flip to label=null at 2.0) is REMOVED, mirroring Python
// 1.17.0: the implicit `label="cli"` default is an explicit, documented,
// contractually-stable default with NO deprecation path — nothing flips at
// 2.0 and no warning fires. (`fireDefaultLabelWarningOnce` and the internal
// latch left with it.)

/** Reset the one-time D-22 default-label FutureWarning latch — TEST USE ONLY. */
/**
* No-op back-compat stub for the removed D-22 warning-latch reset.
*
* @deprecated The one-time D-22 FutureWarning this hook reset was REMOVED in
* 1.17 (the label-default flip was cancelled — Python 34-06 parity), so there
* is no latch left to reset. Kept as a no-op export through 1.x because it
* was previously importable from the root barrel (codex PR-127 review:
* removing a shipped export in a minor breaks downstream compile-time
* imports). Removed at 2.0.
*/
export function _resetDefaultLabelWarning(): void {
_defaultLabelWarned = false;
}

const _D22_BRIDGE_WARNING =
"FutureWarning: dataset() called without an explicit label returns the cli_* " +
"columns today, but the default flips to label=null (features-only) at 2.0. " +
'Pass label="cli" to keep the current output or label=null to opt in early.';

export function fireDefaultLabelWarningOnce(): void {
if (_defaultLabelWarned) return;
console.warn(_D22_BRIDGE_WARNING);
_defaultLabelWarned = true;
// Intentionally empty — the warning latch no longer exists.
}

/**
Expand Down Expand Up @@ -945,21 +943,15 @@ async function fetchGhcnhWithCache(
* column (`ObservationDatasetRow[]`). The default `"daily"` output is
* byte-identical to the pre-31 shape.
*/
// Phase 32 32-05 (D-22) — ALIAS SPLIT. `research`/`dataset` are no longer
// object-identical (`export { research as dataset }`): they are thin wrappers
// over one shared body `_datasetImpl`, whose ONLY divergence is the internal
// `warnOnDefaultLabel` flag (dataset `true`, research `false`). This is the
// same split the Python side made in 32-02 so the two entry points can carry
// divergent default-label warning behavior. See the wrappers below.
//
// The extra `warnOnDefaultLabel` parameter is INTERNAL — the public wrappers
// pin it and re-expose the clean overloaded signature.
// Phase 32 32-05 (D-22) — ALIAS SPLIT, warning REMOVED (34-06 parity).
// `research`/`dataset` stay DISTINCT thin wrappers over one shared body (the
// split shape is pinned by tests), but the `warnOnDefaultLabel` divergence is
// GONE with the cancelled D-22 flip — both entries now behave identically.
async function _datasetImpl(
station: string,
fromDate: string,
toDate: string,
opts: ResearchOptions = {},
warnOnDefaultLabel = false,
): Promise<ReadonlyArray<PairsRow> | ReadonlyArray<ObservationDatasetRow>> {
// ── Phase 21 21-01 kwarg validation (lockstep with Python) ───────────
//
Expand Down Expand Up @@ -991,17 +983,15 @@ async function _datasetImpl(
// Distinguish OMITTED (`undefined`) from explicit `null` (features-only) via
// a sentinel — a JSON-round-tripped Python `label=None` arrives as `null` and
// must behave as features-only, NOT as OMITTED. When OMITTED, resolve to the
// `"cli"` recipe (byte-identical cli_* — the D-22 bridge default) and, on the
// `dataset()` entry only (`warnOnDefaultLabel`), fire the ONE-TIME
// FutureWarning. An explicit label — including `null`/`"cli"` — is honored
// verbatim and NEVER triggers the bridge warning (so pinned parity calls stay
// warning-free). Validate SHAPE up-front, before any network fetch.
// `"cli"` recipe — the EXPLICIT, contractually-stable default (byte-identical
// cli_*; the D-22 flip-to-null-at-2.0 was CANCELLED in 34-06, so no warning
// fires on either entry point). Validate SHAPE up-front, before any network
// fetch.
const labelOmitted = opts.label === undefined;
const resolvedLabel: LabelValue | typeof LABEL_OMITTED = labelOmitted
? LABEL_OMITTED
: (opts.label as LabelValue);
if (!labelOmitted) validateLabel(opts.label as LabelValue);
if (labelOmitted && warnOnDefaultLabel) fireDefaultLabelWarningOnce();

// Phase 21 21-01 D-03: `backend="polars"` is accepted-for-surface but
// rejected at runtime since TS has no Polars equivalent (bundle-size
Expand Down Expand Up @@ -1438,14 +1428,10 @@ function broadcastLabelDelta(
}

// ---------------------------------------------------------------------------
// Phase 32 32-05 (D-22) — the ALIAS-SPLIT public wrappers.
//
// `research` and `dataset` are DISTINCT thin wrappers over the shared
// `_datasetImpl` body. The ONLY divergence is the `warnOnDefaultLabel` flag:
// - `dataset()` → warnOnDefaultLabel = true (omitted-label fires the
// one-time D-22 FutureWarning).
// - `research()` → warnOnDefaultLabel = false (legacy name; keeps implicit
// cli FOREVER, no new warning — dies at 2.0 anyway).
// Phase 32 32-05 (D-22) — the ALIAS-SPLIT public wrappers; warning REMOVED
// (Python 34-06 parity: the flip was cancelled, so the wrappers no longer
// diverge — both keep the explicit, contractually-stable implicit
// `label="cli"` default silently; `research()` retires at 2.0).
// Both carry the FULL explicit overloaded signature so `inspect`-style
// introspection + IDE help stay intact (mirrors the Python split which kept the
// full signature on both wrappers rather than *args/**kwargs).
Expand All @@ -1454,11 +1440,10 @@ function broadcastLabelDelta(
/**
* Leakage-safe composed training table for a station + date window.
*
* `research()` is the legacy entry point: an OMITTED `label` returns the
* `cli_*` columns and NEVER emits the D-22 FutureWarning (the implicit-cli
* behavior is retained for the legacy name until 2.0). Prefer `dataset()` for
* new code — it is the primary documented name and surfaces the D-22 bridge
* warning so callers migrate before the 2.0 default flip.
* `research()` is the legacy entry point (retires at 2.0): an OMITTED `label`
* returns the `cli_*` columns silently. Prefer `dataset()` for new code — it
* is the primary documented name; the two behave identically (the D-22
* default-flip warning was cancelled in 1.17, mirroring Python 34-06).
*
* See {@link ResearchOptions} for the full options surface (label axis,
* granularity, source pin, forecast, cache).
Expand Down Expand Up @@ -1487,19 +1472,19 @@ export async function research(
toDate: string,
opts: ResearchOptions = {},
): Promise<ReadonlyArray<PairsRow> | ReadonlyArray<ObservationDatasetRow>> {
return _datasetImpl(station, fromDate, toDate, opts, /* warnOnDefaultLabel */ false);
return _datasetImpl(station, fromDate, toDate, opts);
}

/**
* Leakage-safe composed training table for a station + date window — the
* PRIMARY documented name (30-04 D-02).
*
* `dataset()` is the general-first entry point: an OMITTED `label` still
* returns the `cli_*` columns byte-identically today, but emits a ONE-TIME
* D-22 FutureWarning ("default flips to label=null at 2.0 — pass label='cli' or
* label=null"). Pass an explicit `label` to silence the bridge and pin the
* output (`"cli"` for today's shape, `null`/`"none"` for features-only,
* `"daily_extremes"` for the WU/NOAA-WRH quantity, or a BYO row-array).
* `dataset()` is the general-first entry point: an OMITTED `label` returns
* the `cli_*` columns — the EXPLICIT, contractually-stable default with no
* deprecation path (the D-22 flip-to-null-at-2.0 was CANCELLED in 1.17,
* mirroring Python 34-06; no warning fires). Pass an explicit `label` to pin
* a different output (`null`/`"none"` for features-only, `"daily_extremes"`
* for the WU/NOAA-WRH quantity, or a BYO row-array).
*
* See {@link ResearchOptions} for the full options surface.
*/
Expand Down Expand Up @@ -1527,5 +1512,5 @@ export async function dataset(
toDate: string,
opts: ResearchOptions = {},
): Promise<ReadonlyArray<PairsRow> | ReadonlyArray<ObservationDatasetRow>> {
return _datasetImpl(station, fromDate, toDate, opts, /* warnOnDefaultLabel */ true);
return _datasetImpl(station, fromDate, toDate, opts);
}
38 changes: 28 additions & 10 deletions packages-ts/meta/tests/label-axis.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Phase 32 32-05 — label-axis INTEGRATION tests (fetch-mocked). Parity with
// Python 32-02/32-03:
// - default/label="cli" byte-stable (cli identity — the D-22 bridge default);
// - dataset() omitted-label FIRES the D-22 FutureWarning ONCE; research()
// omitted-label NEVER warns (the alias split); explicit label never warns;
// - default/label="cli" byte-stable (cli identity — the stable implicit default);
// - neither entry point ever fires a default-label warning (the D-22 flip
// was CANCELLED in 1.17, Python 34-06 parity); explicit label never warns;
// - label="none" drops cli_*; label=null == "none";
// - unknown recipe rejected before fetch;
// - markets.kalshi.dataset delegates to label="cli" + appends label_outcome.

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { _resetDefaultLabelWarning, dataset, research } from "../src/index.js";
import { dataset, research } from "../src/index.js";
import { kalshi } from "../src/marketsDataset.js";

const IEM_HEADER =
Expand Down Expand Up @@ -54,11 +54,9 @@ const CLI: CliMockRecord[] = [

beforeEach(() => {
vi.restoreAllMocks();
_resetDefaultLabelWarning();
});
afterEach(() => {
vi.restoreAllMocks();
_resetDefaultLabelWarning();
});

describe("label='cli' identity + byte-stability (D-22 default)", () => {
Expand All @@ -72,15 +70,19 @@ describe("label='cli' identity + byte-stability (D-22 default)", () => {
});
});

describe("D-22 bridge — dataset() warns once, research() never (alias split)", () => {
it("dataset() omitted-label fires the FutureWarning ONCE", async () => {
describe("D-22 flip CANCELLED — neither entry point ever warns (34-06 parity)", () => {
// Python 34-06 removed the D-22 label-default flip: the implicit label="cli"
// default is EXPLICIT, documented, and contractually stable with NO
// deprecation path — there is nothing to flip at 2.0 and no warning to fire.
// TS mirrors that here (cross-SDK 2.0 parity would break otherwise).
it("dataset() omitted-label NEVER fires a FutureWarning (flip cancelled)", async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
installFetchMock({ cli: CLI });
await dataset("NYC", "2025-01-01", "2025-01-02");
installFetchMock({ cli: CLI });
await dataset("NYC", "2025-01-01", "2025-01-02");
const futureWarns = warn.mock.calls.filter((c) => /FutureWarning/.test(String(c[0])));
expect(futureWarns).toHaveLength(1); // one-time latch across two calls
expect(futureWarns).toHaveLength(0);
});

it("research() omitted-label NEVER fires the FutureWarning", async () => {
Expand All @@ -91,14 +93,22 @@ describe("D-22 bridge — dataset() warns once, research() never (alias split)",
expect(futureWarns).toHaveLength(0);
});

it("dataset() with an EXPLICIT label does NOT fire the bridge warning", async () => {
it("dataset() with an EXPLICIT label does NOT warn either", async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
installFetchMock({ cli: CLI });
await dataset("NYC", "2025-01-01", "2025-01-02", { label: "cli" });
const futureWarns = warn.mock.calls.filter((c) => /FutureWarning/.test(String(c[0])));
expect(futureWarns).toHaveLength(0);
});

it("omitted-label dataset() stays byte-identical to explicit label='cli'", async () => {
installFetchMock({ cli: CLI });
const omitted = await dataset("NYC", "2025-01-01", "2025-01-02");
installFetchMock({ cli: CLI });
const explicit = await dataset("NYC", "2025-01-01", "2025-01-02", { label: "cli" });
expect(JSON.parse(JSON.stringify(omitted))).toEqual(JSON.parse(JSON.stringify(explicit)));
});

it("research and dataset are DISTINCT callables (alias split)", () => {
expect(research).not.toBe(dataset);
});
Expand Down Expand Up @@ -178,3 +188,11 @@ describe("markets.kalshi.dataset — thin cli delegator + outcome", () => {
expect(rows).toHaveLength(1);
});
});

describe("_resetDefaultLabelWarning — deprecated no-op back-compat stub", () => {
it("stays importable from the root barrel and no-ops (removed at 2.0)", async () => {
const mod = await import("../src/index.js");
expect(typeof mod._resetDefaultLabelWarning).toBe("function");
expect(mod._resetDefaultLabelWarning()).toBeUndefined();
});
});
16 changes: 6 additions & 10 deletions packages-ts/meta/tests/panels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { research } from "../src/index.js";
import { LabelAlignmentError } from "../src/labels.js";
import { datasetPanel } from "../src/panels.js";
import { _resetDefaultLabelWarning } from "../src/research.js";

const IEM_HEADER =
"station,valid,tmpf,dwpf,drct,sknt,gust,alti,mslp,vsby,skyc1,skyl1,skyc2,skyl2,skyc3,skyl3,skyc4,skyl4,wxcodes,p01i,snowdepth,peak_wind_gust,peak_wind_drct,peak_wind_time,metar";
Expand Down Expand Up @@ -132,19 +131,16 @@ describe("datasetPanel — long-format multi-station fan-out", () => {
});
});

// FIX-5 (D-22): a panel called WITHOUT an explicit label fires the bridge
// FutureWarning EXACTLY once (never N-per-station); a panel WITH an explicit
// label fires zero. Mirrors Python `_compose_panel`.
describe("datasetPanel — D-22 default-label FutureWarning (FIX-5)", () => {
beforeEach(() => _resetDefaultLabelWarning());
afterEach(() => _resetDefaultLabelWarning());

it("omitted-label panel warns exactly once (not once per station)", async () => {
// D-22 flip CANCELLED (34-06 parity): panels never fire a default-label
// warning — omitted-label keeps the explicit, contractually-stable
// label="cli" default silently, like Python.
describe("datasetPanel — D-22 flip cancelled: no default-label warning", () => {
it("omitted-label panel warns zero", async () => {
installFetchMock(CLI);
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
await datasetPanel("2025-01-01", "2025-01-02", { stations: ["NYC", "LAX"] });
const d22 = warnSpy.mock.calls.filter((c) => String(c[0]).includes("label"));
expect(d22).toHaveLength(1);
expect(d22).toHaveLength(0);
});

it("explicit-label panel warns zero", async () => {
Expand Down
Loading