mostlyright.economy
mostlyright.economy — economic-indicator data for prediction-market settlement.
Macro releases — CPI (headline / core / YoY), PPI, nonfarm payrolls (NFP) and revisions, U3 unemployment, initial jobless claims, GDP, and Fed funds decisions — sourced from FRED/ALFRED + BLS/BEA/DOL/Federal Reserve and joined to Kalshi and Polymarket economy markets for leakage-free settlement pairs.
This package is deliberately isolated from the four weather parity files
(research(), _internal/merge/observations.py’s SOURCE_PRIORITY,
_internal/merge/climate.py’s policies, _live/_sources.py) — the same
firewall CWOP sits behind. Economy carries its own
schema.economy.observations.v1, its own per-release cache namespace
(~/.mostlyright/cache/v1/economy/…), and its own error types
(IndicatorNotYetReleasedError). A macro release must never be routed through
the weather settlement join, and the weather merge code must never see an
economy row.
Load-bearing invariant: Kalshi settlements contractually exclude
post-expiration revisions, so these functions return as-released vintages, never
the revised series. Vintage selection happens at read time via the vintages
keyword.
These functions follow the source-identity contract documented in
docs/source-identity.md: source= is always provenance (which agency
produced the row) and delivery= is always where the computation runs
("live" locally, "hosted" reserved). Both are validated before any
network call.
Public surface:
series()— observation rows for an indicator across a date range (canonical read;source=/delivery=contract kwargs).snapshot()— the settlement-target state as-of a cutoff (the latest settlement-grade vintage knowable atas_of).release_calendar()— the point-in-time release schedule: when agencies said releases would happen, as known at any past moment (as_of=). A reschedule is a new row rather than an overwrite, so a past view stays reconstructable. Local only: nodelivery=keyword, no hosted call.releases()— the release calendar / schedule for an indicator.training_table()— a small, capped settlement-pair example.attribution()— the licence attribution statements the rows in a frame require, derived from itsindicatorandagencycolumns so the answer survives a join, a concat and a JSON row round-trip.__version__— the installedmostlyrightmd-economydistribution version (parity with theweather/marketssiblings).
exception mostlyright.economy.IndicatorNotYetReleasedError(indicator, period, , expected_release=None, remedy=None, source=None, request_id=None, error_code=None)
Section titled “exception mostlyright.economy.IndicatorNotYetReleasedError(indicator, period, , expected_release=None, remedy=None, source=None, request_id=None, error_code=None)”Bases: MostlyrightError
A requested economic release is EXPECTED but has not been published yet.
For economic releases, “not yet released” is a normal, branchable state
distinct from “unavailable”: the release calendar says a print is due, but
no vintage_time exists for that period yet. The economy verbs
(economy.series / economy.releases / economy.training_table)
raise this instead of returning [] / None, so a caller can tell “the
data is genuinely gone” (a DataAvailabilityError) apart from “come
back after the 8:30 ET release” (this error) and, when the time is known,
retry at expected_release.
Defined in core rather than packages/economy for the same reason as
EarningsError and NoCWOPDataError: the exception taxonomy
is centralized so MCP JSON-RPC serialization and Python/TypeScript lockstep
apply uniformly. mostlyright.economy re-exports it.
- Parameters:
- Return type: None
indicator
Section titled “indicator”The indicator id ("cpi", "nfp", "gdp", …).
period
Section titled “period”The observation period requested ("2026-06", "2026Q2",
an FOMC meeting date, …).
expected_release
Section titled “expected_release”The scheduled release wall-clock when known, else
None (the payload never fabricates a timestamp).
remedy
Section titled “remedy”An optional actionable hint for how to unlock the value (for
example, that the settlement-grade first print needs
FRED_API_KEY). It has its own field — included in the message
body and in _payload() — so period stays a real observation
period and never carries a hint.
default_error_code: str
Section titled “default_error_code: str”Subclass override — the stable string enum surfaced via error_code.
error_code: str
Section titled “error_code: str”expected_release: datetime | None
Section titled “expected_release: datetime | None”indicator: str
Section titled “indicator: str”message: str
Section titled “message: str”period: str
Section titled “period: str”remedy: str | None
Section titled “remedy: str | None”request_id: str | None
Section titled “request_id: str | None”source: str | None
Section titled “source: str | None”exception mostlyright.economy.ReleaseScheduleUnavailableError(message=”, , source=None, http_status=None, retryable=False, retry_after_s=None, underlying=”, url=None, request_id=None, error_code=None)
Section titled “exception mostlyright.economy.ReleaseScheduleUnavailableError(message=”, , source=None, http_status=None, retryable=False, retry_after_s=None, underlying=”, url=None, request_id=None, error_code=None)”Bases: SourceUnavailableError
A release-schedule source answered, but the body carried no usable schedule.
Raised when a schedule leg returns a body that parses to zero usable events, or an event whose release-type token is outside the mapped vocabulary. An empty schedule is an explicit error, never a silently-empty return: a caller that receives an empty calendar reads it as “every future release is cancelled”, which is the wrong answer to a bot-wall page, an HTML landing page served at HTTP 200, or an unfolded line the parser dropped.
Subclasses SourceUnavailableError so an existing
except SourceUnavailableError block still catches it, and inherits that
constructor unchanged — source= / url= / http_status= carry the
same meaning. The distinct error_code (RELEASE_SCHEDULE_UNAVAILABLE,
shared byte-for-byte with the TypeScript mirror) is what lets a caller that
cares tell “the endpoint answered with nothing usable” apart from “the
endpoint was unreachable”.
- Parameters:
- Return type: None
default_error_code: str
Section titled “default_error_code: str”Subclass override — the stable string enum surfaced via error_code.
error_code: str
Section titled “error_code: str”http_status: int | None
Section titled “http_status: int | None”message: str
Section titled “message: str”request_id: str | None
Section titled “request_id: str | None”retry_after_s: float | None
Section titled “retry_after_s: float | None”retryable: bool
Section titled “retryable: bool”source: str | None
Section titled “source: str | None”underlying: str
Section titled “underlying: str”mostlyright.economy.attribution(df)
Section titled “mostlyright.economy.attribution(df)”Return every attribution statement the rows in df require.
Reads the indicator column (an observation frame) and the agency
column (a release-schedule frame), maps each distinct value onto the
statements its licence requires, and returns the union with duplicates
removed, sorted so the result does not depend on row order. A frame carrying
both columns is read on both.
An indicator resolves through ATTRIBUTION_BY_INDICATOR first and
through ATTRIBUTION_BY_AREA only if it has no entry there. Statements
that name a reference date — the Statistics Canada acknowledgment is the one
today — read it from the LATEST period in that id’s own rows, so a
narrower frame produces a narrower claim.
Publishing results derived from ONS rows requires reproducing the returned statement. US rows return an empty tuple: those bytes are public domain.
df is never modified, and df.attrs is never consulted — the whole
point is that the answer comes from data that survives a join and a JSON row
round-trip, which frame-level metadata does not.
- Parameters:
df (
DataFrame) – Any DataFrame carrying anindicatorcolumn, anagencycolumn, or both. Null entries in either column are skipped:indicatoris nullable on the release-schedule schema, and an absent value is not an id. - Return type:
tuple[str,...] - Returns: The sorted, de-duplicated statements. Empty when every row’s licence requires nothing.
- Raises:
- ValueError –
dfcarries neither column, so there is nothing to derive the obligation from; or a value names a reference area or an agency with no entry in the maps above; or an id’s statement names a reference date and the frame has noperiodvalue for its rows. All three are loud on purpose — silently answering “no obligation” for data this cannot read, or an acknowledgment with a required field missing, are the two outcomes that would put a caller in breach. - TypeError – an
indicatorentry is not a string (raised by the id grammar, which is where what an id may be is decided).
- ValueError –
mostlyright.economy.release_calendar(indicator=None, , agency=None, as_of=None, from_date=None, to_date=None, refresh=False, client=None)
Section titled “mostlyright.economy.release_calendar(indicator=None, , agency=None, as_of=None, from_date=None, to_date=None, refresh=False, client=None)”Return the agency release schedule as a schema.economy.releases.v1 frame.
On a cold cache this captures from every available tier, persists what the
licensing gate permits, and answers from the store plus the rows the gate
refused to write. On a warm cache it reads only — refresh=True forces a
new capture.
“Warm” is judged against the requested scope, not the whole store. The
curated Federal Reserve tier always persists, so a store-wide check reads as
warm even when the leg the caller needs has never landed: after a first
capture with both BEA legs down, every later indicator="gdp" call would
skip the capture, filter to nothing and raise, permanently. The cost of
scoping it is that an indicator with no coverage at all re-captures on each
call before raising.
FRED rows are fetch-through. FRED’s Terms of Use ban store/cache/archive,
so without MOSTLYRIGHT_PERSIST_FRED=1 its rows answer the call that
fetched them and are then discarded, which also means a FRED-scoped call
re-captures every time because nothing was kept to serve the next one. They
are still recognizable in the returned frame: agency='FRED' and
time_precision='date_only'. The frame-level source tag stays
economy.calendar.cache for every row, because a frame carries one
provenance tag and the schema validator requires the row column to match it.
- Parameters:
- indicator (
str|None) – restrict to one economy indicator.'gdp'reaches the BEA leg;'fed_funds'/'fed_decision'reach the curated Federal Reserve tier. An indicator with no calendar coverage raises rather than returning an empty frame — see the coverage split in the module docstring. - agency (
str|None) – restrict to one publishing agency ('BEA','FederalReserve','FRED'). - as_of (
datetime|None) – return the schedule as it was known at this instant. Rows captured later are invisible, and per release the newest surviving capture wins. Default (None) is now: the current schedule. - to_date (datetime | None) – bound the release instants returned. A cancelled row carries no release instant, so bounding excludes tombstones.
- refresh (
bool) – re-capture even when the store already holds rows. - client (
Client|None) – an injectedhttpx.Client(tests / connection reuse). - from_date (datetime | None)
- to_date
- indicator (
- Return type:
DataFrame - Returns:
A
schema.economy.releases.v1DataFrame stampedsource='economy.calendar.cache'— the rows were read back from the local store. There is nodelivery=keyword and no hosted call. - Raises: ReleaseScheduleUnavailableError – the requested scope holds no row. Never an empty frame: an empty schedule read back as data says every future release was cancelled.
mostlyright.economy.releases(indicator)
Section titled “mostlyright.economy.releases(indicator)”Return the release calendar / schedule for indicator.
- Parameters:
indicator (
str) – An economy indicator id (schema.economy.observations.v1vocabulary:"cpi"/"nfp"/"gdp"/"fed_funds"/"jobless_claims"/ …). - Return type:
list[ReleaseEvent] - Returns:
The scheduled releases for the indicator as a list of
ReleaseEvent, sorted byrelease_datetimeascending. Sourced from the curated schedule table. - Raises:
- TypeError –
indicatoris not astr. - ValueError –
indicatorhas no known schedule (never returns[]/None— an unschedulable indicator is an explicit error).
- TypeError –
mostlyright.economy.series(indicator, from_date, to_date, , vintages=‘settlement’, source=None, delivery=‘live’)
Section titled “mostlyright.economy.series(indicator, from_date, to_date, , vintages=‘settlement’, source=None, delivery=‘live’)”Return economic-indicator observation rows for indicator (canonical).
series is the canonical economy read function and follows the
cross-domain source-identity keyword contract (docs/source-identity.md).
- Parameters:
- indicator (
str) – The indicator id ("cpi"/"nfp"/"gdp"/"ppi"/"jobless_claims"/ …; theschema.economy.observations.v1vocabulary). - from_date (
date|datetime) – Inclusive start of the requested range. - to_date (
date|datetime) – Inclusive end of the requested range. - vintages (
str) –"settlement"(default) returns only the settlement-grade first-print rows (the value as-of the Kalshi expiration);"all"returns every vintage for feature engineering.settlementrows are always a subset ofallrows (the clean-partition property). - source (
str|None) – Provenance pin (contract §1).None(default) uses the per-indicator default routing. A pin names the authority ("fred"ALFRED vintage store,"bls","bea","dol","fed"). An unknown source, or a valid authority that cannot serveindicator(e.g.source="bea"for"cpi"), raisesValueErrorbefore any network call — never a silent fallback. - delivery (
str) – Where the computation runs (contract §2)."live"(default) hits the public agency APIs locally;"hosted"is the reserved precomputed-API channel and raisesSourceUnavailableErrornamingMOSTLYRIGHT_ECONOMY_HOSTED_URL+MOSTLYRIGHT_API_KEY. An unknown value raisesValueErrorbefore any network call.
- indicator (
- Return type:
DataFrame - Returns:
A
schema.economy.observations.v1pandas.DataFrame(validated). - Raises:
- ValueError –
vintagesis not"settlement"/"all"; an invalidsource/delivery(before any network call); orindicatorhas no dispatch entry, which never returns a silent empty frame. - SourceUnavailableError –
delivery="hosted", the reserved channel. - DataAvailabilityError –
reason="out_of_window"whenfrom_dateis below the indicator’s FEDS-2026-010 first-contract floor. - IndicatorNotYetReleasedError – the window has no relevant rows — a scheduled
release has not landed yet. It never returns
[]/None. - ImportError – pandas is not installed (it normally arrives with
mostlyrightmd-core).
- ValueError –
Example
Section titled “Example”>>> df = series("cpi", "2024-01-01", "2024-06-30")>>> df.columns[:2].tolist()['indicator', 'period']mostlyright.economy.snapshot(indicator, , as_of=None, source=None, delivery=‘live’)
Section titled “mostlyright.economy.snapshot(indicator, , as_of=None, source=None, delivery=‘live’)”Return the settlement-target state of indicator as knowable at as_of.
- Parameters:
- indicator (
str) – The indicator id ("cpi"/"nfp"/"gdp"/"ppi"/"jobless_claims"/ …; theschema.economy.observations.v1vocabulary). - as_of (
datetime|None) – The knowledge-time cutoff. Only vintages whosevintage_timeis at or beforeas_ofare considered. Defaults to now (UTC-aware) — the current settlement-target state. - source (
str|None) – Provenance pin (contract §1), forwarded toseries().None(default) uses the per-indicator default routing; an unknown source or a valid authority that cannot serveindicatorraisesValueErrorpre-network. - delivery (
str) – Where the computation runs (contract §2), forwarded toseries."live"(default) |"hosted"(reserved; raisesSourceUnavailableError).
- indicator (
- Return type:
DataFrame - Returns:
A
schema.economy.observations.v1pandas.DataFrame— one settlement-grade row per period knowable atas_of(the latest such vintage per period). - Raises:
- ValueError – an invalid
source/delivery(pre-network) — seeseries. - SourceUnavailableError –
delivery="hosted"(reserved, not served). - DataAvailabilityError –
indicatoris unknown (no FEDS floor). - IndicatorNotYetReleasedError – nothing is knowable at
as_of— no settlement-grade vintage exists at or before the cutoff. It never returns[]orNone. - ImportError – pandas is not installed (it normally arrives with
mostlyrightmd-core).
- ValueError – an invalid
mostlyright.economy.training_table(entity, from_date, to_date, , as_of=None, source=None)
Section titled “mostlyright.economy.training_table(entity, from_date, to_date, , as_of=None, source=None)”Leakage-guarded economy supervised table for entity (the quickstart one-liner).
A capped quickstart over the first-print settlement join: resolves the
Kalshi/Polymarket economy contract (or series root) to its settlement-grade
first-print agency vintage and returns training pairs. Two positional dates,
inclusive ends. as_of and source forward to that join.
- Parameters:
- entity (
str) – A Kalshi economy series root ("KXCPIYOY") or a concrete dated market ticker ("KXCPIYOY-26JUL"). Case-insensitive. - from_date (
date|datetime) – Inclusive start of the requested range. - to_date (
date|datetime) – Inclusive end of the requested range. - as_of (
TimePoint|None) – Optional leakage cutoff (aTimePoint); any pair whoseknowledge_timeis after it raisesLeakageError— a future revision can never leak into research. - source (
str|None) – Provenance pin (source-identity contract §1), forwarded verbatim to the underlying join (None= per-indicator default routing).
- entity (
- Return type:
DataFrame - Returns:
The economy settlement-pairs
pandas.DataFrame.
:raises TypeError` / ValueError / SourceUnavailableError :
:raises IndicatorNotYetReleasedError` / LeakageError / ImportError — th:
:raises typed errors the underlying settlement join raises.:
For anything beyond the capped quickstart, compose the pieces yourself —
economy.series() / economy.snapshot() joined through
mostlyright.experimental (align / spine), the documented
composition path. The five-parameter cap is not widened with new
keywords; every top-level domain verb keeps the same grammar.