@mostlyrightmd/markets 0.1.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,317 @@
1
+ import { TradewindsError } from '@mostlyrightmd/core';
2
+ import { InternationalRow } from '@mostlyrightmd/core/discovery';
3
+
4
+ /** Raw Gamma event payload — narrow shape we depend on. */
5
+ interface PolymarketEventRaw {
6
+ id?: string;
7
+ slug?: string;
8
+ title?: string;
9
+ description?: string;
10
+ endDate?: string;
11
+ active?: boolean;
12
+ closed?: boolean;
13
+ archived?: boolean;
14
+ tags?: Array<string | {
15
+ label?: string;
16
+ slug?: string;
17
+ }>;
18
+ [key: string]: unknown;
19
+ }
20
+ interface FetchEventsOptions {
21
+ /** Politeness sleep between requests, in ms. Default 200 (0.2 s). Pass 0 to skip. */
22
+ readonly sleepBetweenMs?: number;
23
+ /** AbortSignal for the whole paginator. */
24
+ readonly signal?: AbortSignal;
25
+ /** Override fetch (for tests). Defaults to global fetch. */
26
+ readonly fetchFn?: typeof fetch;
27
+ }
28
+ /**
29
+ * Fetch every active weather event from Gamma, paginated by `offset` in
30
+ * `PAGE_SIZE` increments until either an empty page is returned or
31
+ * `MAX_EVENTS` is reached. Dedup by slug to defend against the rare case
32
+ * where pagination overlaps under concurrent edits on Gamma's side.
33
+ */
34
+ declare function fetchEvents(opts?: FetchEventsOptions): Promise<PolymarketEventRaw[]>;
35
+ /** Fetch a single event by id. Useful for the settle() flow when only an id is known. */
36
+ declare function fetchEventById(eventId: string, opts?: FetchEventsOptions): Promise<PolymarketEventRaw | null>;
37
+
38
+ /**
39
+ * Resolution-source netlocs we trust. Anything else throws
40
+ * PolymarketEventError. Mirrors Python `RESOLUTION_SOURCE_ALLOWLIST`.
41
+ */
42
+ declare const RESOLUTION_SOURCE_ALLOWLIST: ReadonlySet<string>;
43
+ /**
44
+ * Per-netloc → enum value for the `resolutionSourceType` field on settlement
45
+ * records. `hko` / `cwa` are predeclared for v0.2 (HKO/CWA clients).
46
+ */
47
+ declare const NETLOC_TO_RESOLUTION_TYPE: Readonly<Record<string, string>>;
48
+ /** Enum values for the `resolutionSourceType` column. */
49
+ declare const POLYMARKET_RESOLUTION_SOURCE_TYPES: readonly ["wunderground", "noaa_wrh", "hko", "cwa", "other"];
50
+ type PolymarketResolutionSourceType = (typeof POLYMARKET_RESOLUTION_SOURCE_TYPES)[number];
51
+ /**
52
+ * Event id pattern. Python widened this in codex iter-2 P1: real Gamma IDs
53
+ * are numeric strings (`"12345"`), but condition-tag UUIDs + slugs also
54
+ * appear in the wild. Wide enough to accept real Gamma payloads but narrow
55
+ * enough to defend against URL-path injection.
56
+ *
57
+ * The plan called this "UUID4 regex" but follows Python's actual behavior:
58
+ * the strict UUID4 form rejected every real Gamma event, breaking the
59
+ * discover → settle round-trip.
60
+ */
61
+ declare const EVENT_ID_RE: RegExp;
62
+ /**
63
+ * Max bytes of a Polymarket event description we'll parse. Polymarket
64
+ * descriptions are concise; oversized payloads indicate hostile input
65
+ * (ReDoS defense).
66
+ */
67
+ declare const MAX_DESCRIPTION_BYTES: number;
68
+ /**
69
+ * Per-resolution-source publication delay. Settlement refuses to settle
70
+ * until `now - settlementDate >= delay` to avoid settling on values the
71
+ * issuer hasn't published yet.
72
+ *
73
+ * Wunderground typically posts daily extremes ~6h after local midnight;
74
+ * NOAA WRH ~4h. "other" gets a conservative 24h fallback.
75
+ */
76
+ declare const SETTLE_DELAY_HOURS: Readonly<Record<string, number>>;
77
+ /**
78
+ * Slug date extractor. Polymarket weather slugs embed the resolution date
79
+ * (e.g. `will-nyc-be-above-80f-on-2026-05-23`). Used by `polymarketSettle`
80
+ * to derive the resolution date from the slug instead of `event.endDate`.
81
+ */
82
+ declare const SLUG_DATE_RE: RegExp;
83
+ /** Markets routed to v0.2 sources (CWA/HKO clients). */
84
+ declare const DEFERRED_STATIONS: ReadonlySet<string>;
85
+ /** Discovery row shape — one per active weather event. */
86
+ interface PolymarketDiscoveryRow {
87
+ readonly eventId: string | null;
88
+ readonly slug: string | null;
89
+ readonly title: string | null;
90
+ readonly city: string | null;
91
+ readonly icao: string | null;
92
+ readonly measure: "high" | "low" | "default" | null;
93
+ readonly endTime: string | null;
94
+ readonly resolutionSourceType: PolymarketResolutionSourceType | null;
95
+ }
96
+ /**
97
+ * Native unit of the market's published settlement value. Codex iter-3 P2:
98
+ * international Polymarket markets publish in whole-°C, US in °F. The
99
+ * settle engine returns the resolved value in BOTH units so the caller's
100
+ * comparison against Polymarket's published value uses the matching unit.
101
+ */
102
+ type SettlementUnit = "fahrenheit" | "celsius";
103
+ /** Settlement result shape. */
104
+ interface PolymarketSettlementResult {
105
+ readonly eventId: string;
106
+ readonly settlementDate: string;
107
+ readonly icao: string;
108
+ readonly measure: "high" | "low" | "default";
109
+ /**
110
+ * Resolved temperature in the unit the caller asked for (the `unit`
111
+ * option; defaults to the station's native unit — F for US-registry
112
+ * stations, C for international). Convenience pointer to `resolvedValueF`
113
+ * or `resolvedValueC`.
114
+ */
115
+ readonly resolvedValue: number;
116
+ readonly resolvedValueC: number;
117
+ readonly resolvedValueF: number;
118
+ /** Which unit `resolvedValue` carries. */
119
+ readonly unit: SettlementUnit;
120
+ readonly resolutionSourceType: PolymarketResolutionSourceType;
121
+ readonly dataQualityAlert: string | null;
122
+ }
123
+ /** Settlement options. */
124
+ interface PolymarketSettleOptions {
125
+ /** Optional description override (live discovery normally supplies this). */
126
+ readonly description?: string;
127
+ /** Reference "now" for the finalization-delay check. Defaults to `new Date()`. */
128
+ readonly now?: Date;
129
+ /**
130
+ * Polymarket's published settlement value, if known. The comparison
131
+ * uses whichever unit `unit` is set to. ±1°F (or ±0.6°C) diff emits an
132
+ * alert; values outside that band don't throw.
133
+ */
134
+ readonly polymarketPublishedValue?: number;
135
+ /**
136
+ * Resolved-value unit. Defaults to the station's native unit:
137
+ * °F for the 20 US Kalshi cities; °C for international stations
138
+ * (matches Polymarket's published-bucket convention per
139
+ * .planning/research/INGEST-PLANNER-RESEARCH.md). Codex iter-3 P2.
140
+ */
141
+ readonly unit?: SettlementUnit;
142
+ }
143
+
144
+ /**
145
+ * Apply the 16 KB cap + netloc allowlist to a description string.
146
+ *
147
+ * @throws PayloadTooLargeError when the UTF-8 byte length exceeds 16 KB.
148
+ * @throws PolymarketEventError when any URL has a netloc outside the allowlist.
149
+ */
150
+ declare function validateDescription(description: string): void;
151
+ /**
152
+ * Classify a description's resolution source by the first allowlisted netloc
153
+ * found. Returns `"other"` when no allowlisted URL appears (the settlement
154
+ * engine falls back to the 24-hour delay for "other").
155
+ */
156
+ declare function extractResolutionSourceType(description: string): PolymarketResolutionSourceType;
157
+
158
+ /** Event payload is malformed (bad event id, oversized description, bad URL). */
159
+ declare class PolymarketEventError extends TradewindsError {
160
+ constructor(message: string);
161
+ static readonly defaultErrorCode = "POLYMARKET_EVENT_INVALID";
162
+ }
163
+ /** Settlement engine couldn't resolve an event to a value. */
164
+ declare class PolymarketSettlementError extends TradewindsError {
165
+ constructor(message: string);
166
+ static readonly defaultErrorCode = "POLYMARKET_SETTLEMENT_FAILED";
167
+ }
168
+ /**
169
+ * Settlement attempted before the resolution source's publication delay
170
+ * has elapsed. Carries `waitHours` so the caller can schedule a retry.
171
+ *
172
+ * Codex iter-1 P2: overrides `payload()` so `toDict()` (and any MCP
173
+ * serializer downstream) includes the structured retry metadata. The
174
+ * fields are otherwise only readable via the live JS error object.
175
+ */
176
+ declare class TooEarlyToSettleError extends TradewindsError {
177
+ readonly waitHours: number;
178
+ readonly resolutionSourceType: string;
179
+ constructor(message: string, opts: {
180
+ waitHours: number;
181
+ resolutionSourceType: string;
182
+ });
183
+ static readonly defaultErrorCode = "POLYMARKET_TOO_EARLY";
184
+ protected payload(): Record<string, unknown>;
185
+ }
186
+ /**
187
+ * Description exceeded the 16 KB cap. Direct subclass of TradewindsError
188
+ * (rather than PolymarketEventError) because TS prevents narrowing a
189
+ * `static readonly` literal type in a subclass.
190
+ */
191
+ declare class PayloadTooLargeError extends TradewindsError {
192
+ constructor(message: string);
193
+ static readonly defaultErrorCode = "POLYMARKET_PAYLOAD_TOO_LARGE";
194
+ }
195
+
196
+ interface PolymarketDiscoverOptions extends FetchEventsOptions {
197
+ /**
198
+ * Sink for dropped events. Tests pass a recorder; production can pass
199
+ * console.info or omit. Receives `{slug, reason}` per skipped event.
200
+ */
201
+ onSkip?: (info: {
202
+ slug: string | null;
203
+ reason: string;
204
+ }) => void;
205
+ }
206
+ /**
207
+ * Discover active Polymarket weather events.
208
+ *
209
+ * Returns one row per resolvable event. Events the resolver can't match
210
+ * are dropped silently (with optional `onSkip` callback). Events that
211
+ * route to a deferred station (Taipei, HK-low) appear in the result with
212
+ * `icao: null` and `measure: null` so callers can SEE them.
213
+ */
214
+ declare function polymarketDiscover(opts?: PolymarketDiscoverOptions): Promise<PolymarketDiscoveryRow[]>;
215
+
216
+ declare const POLYMARKET_KNOWN_WRONG_STATIONS: Readonly<Record<string, ReadonlySet<string>>>;
217
+
218
+ /**
219
+ * Detect whether the market resolves on the daily HIGH or LOW from
220
+ * keywords in the event title/slug/name. Distinct from the station-level
221
+ * measure: many cities have one airport for both, but the market still
222
+ * resolves on tmax XOR tmin.
223
+ */
224
+ declare function detectMarketMeasure(event: PolymarketEventRaw): "high" | "low" | "default";
225
+ /**
226
+ * Derive a city key from slug + title + tags. Lowercase substring match
227
+ * against the catalog; longest-first so multi-token cities outrank prefixes.
228
+ * Returns null when no match — caller decides whether to drop or surface.
229
+ */
230
+ declare function deriveCity(event: PolymarketEventRaw): string | null;
231
+ /**
232
+ * Extract the canonical Wunderground PWS / airport ICAO from `text`.
233
+ *
234
+ * Tier 1.5 of the resolver chain — runs between explicit `event.city`
235
+ * and slug-derive. When a Polymarket event embeds a Wunderground PWS
236
+ * URL, the URL IS the source of truth; no catalog lookup needed.
237
+ *
238
+ * Multi-URL disambiguation: when multiple canonical Wunderground URLs
239
+ * appear, ALL extracted ICAOs MUST agree. Disagreement returns null so
240
+ * the resolver falls through to Tier 2 city-derive (prevents an
241
+ * issuer-side citation URL from silently swapping the settlement station).
242
+ *
243
+ * Returns uppercase ICAO (4 chars, leading K) when a canonical URL is
244
+ * found AND any additional canonical URLs agree. Null otherwise —
245
+ * including the disagreement case.
246
+ */
247
+ declare function extractIcaoFromResolutionSource(text: string | null | undefined): string | null;
248
+ /**
249
+ * Resolve an event to `{icao, stationMeasure}` using the city catalog.
250
+ *
251
+ * Returns null when no city matches (caller drops the event).
252
+ * Raises DeferredMarketError when the resolution would route to a v0.2
253
+ * source (Taipei RCTP, Hong Kong VHHH for the low-extreme market).
254
+ */
255
+ declare function resolveStationForEvent(event: PolymarketEventRaw, marketMeasure: "high" | "low" | "default"): {
256
+ city: string;
257
+ icao: string;
258
+ stationMeasure: "high" | "low" | "default";
259
+ } | null;
260
+ /**
261
+ * Parse the resolution date from a Polymarket weather slug. The LAST
262
+ * YYYY-MM-DD match wins because slugs may carry both a creation date and
263
+ * a resolution date (`created-2026-01-01-resolves-2026-05-23`) — the
264
+ * resolution date is typically rightmost in Polymarket's convention.
265
+ *
266
+ * Mirrors Python `_settlement_date_from_slug` architect iter-1 HIGH-4.
267
+ */
268
+ declare function settlementDateFromSlug(slug: string): string;
269
+
270
+ /**
271
+ * Loader contract for the resolution-source observation rows. Defaults to
272
+ * a no-op stub so the security/validation gates can be unit-tested without
273
+ * pulling live cache data; production callers wire the cache reader here.
274
+ *
275
+ * Returning an empty array signals "no rows available for this date" and
276
+ * surfaces as PolymarketSettlementError downstream.
277
+ */
278
+ type ObservationLoader = (args: {
279
+ icao: string;
280
+ fromDate: string;
281
+ toDate: string;
282
+ }) => Promise<ReadonlyArray<InternationalRow>>;
283
+ interface PolymarketSettleArgs extends PolymarketSettleOptions {
284
+ /**
285
+ * The raw Gamma event payload. Required because the settle engine reads
286
+ * `slug` (for the resolution date), `description` (for the resolution
287
+ * source), and `title/slug/name` (for the measure). Callers can fetch
288
+ * via `fetchEventById` if they only have an id.
289
+ */
290
+ readonly event: PolymarketEventRaw;
291
+ /**
292
+ * Loader that returns observation rows for `[fromDate, toDate]`. Defaults
293
+ * to an in-memory empty loader so callers MUST wire this for production.
294
+ */
295
+ readonly loader?: ObservationLoader;
296
+ }
297
+ /**
298
+ * Settle a single Polymarket event.
299
+ *
300
+ * Validates the event id, description (16 KB cap, netloc allowlist),
301
+ * resolves the station via the city catalog, parses the resolution date
302
+ * from the slug, enforces the per-source publication-delay window, and
303
+ * pulls the daily extreme via `internationalDailyExtremes`.
304
+ *
305
+ * @throws PolymarketEventError on bad id / bad description / unsupported station.
306
+ * @throws PolymarketSettlementError when no rows resolve for the station/date.
307
+ * @throws TooEarlyToSettleError when the publication delay hasn't elapsed.
308
+ */
309
+ declare function polymarketSettle(args: PolymarketSettleArgs): Promise<PolymarketSettlementResult>;
310
+ /**
311
+ * Settle by event id alone. Fetches the event from Gamma first, then
312
+ * delegates to `polymarketSettle`. Useful when a caller only has the id
313
+ * (e.g. from a Kalshi-side cross-reference).
314
+ */
315
+ declare function polymarketSettleById(eventId: string, args: Omit<PolymarketSettleArgs, "event">): Promise<PolymarketSettlementResult>;
316
+
317
+ export { DEFERRED_STATIONS, EVENT_ID_RE, type FetchEventsOptions, MAX_DESCRIPTION_BYTES, NETLOC_TO_RESOLUTION_TYPE, type ObservationLoader, POLYMARKET_KNOWN_WRONG_STATIONS, POLYMARKET_RESOLUTION_SOURCE_TYPES, PayloadTooLargeError, type PolymarketDiscoverOptions, type PolymarketDiscoveryRow, PolymarketEventError, type PolymarketEventRaw, type PolymarketResolutionSourceType, type PolymarketSettleArgs, type PolymarketSettleOptions, PolymarketSettlementError, type PolymarketSettlementResult, RESOLUTION_SOURCE_ALLOWLIST, SETTLE_DELAY_HOURS, SLUG_DATE_RE, type SettlementUnit, TooEarlyToSettleError, deriveCity, detectMarketMeasure, extractIcaoFromResolutionSource, extractResolutionSourceType, fetchEventById, fetchEvents, polymarketDiscover, polymarketSettle, polymarketSettleById, resolveStationForEvent, settlementDateFromSlug, validateDescription };