@mostlyrightmd/core 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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/discovery/index.cjs +1646 -0
  4. package/dist/discovery/index.cjs.map +1 -0
  5. package/dist/discovery/index.d.cts +313 -0
  6. package/dist/discovery/index.d.ts +313 -0
  7. package/dist/discovery/index.mjs +1609 -0
  8. package/dist/discovery/index.mjs.map +1 -0
  9. package/dist/formats/index.cjs +498 -0
  10. package/dist/formats/index.cjs.map +1 -0
  11. package/dist/formats/index.d.cts +97 -0
  12. package/dist/formats/index.d.ts +97 -0
  13. package/dist/formats/index.mjs +465 -0
  14. package/dist/formats/index.mjs.map +1 -0
  15. package/dist/index.cjs +1624 -0
  16. package/dist/index.cjs.map +1 -0
  17. package/dist/index.d.cts +559 -0
  18. package/dist/index.d.ts +559 -0
  19. package/dist/index.global.js +1582 -0
  20. package/dist/index.global.js.map +1 -0
  21. package/dist/index.mjs +1557 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/internal/bounds.cjs +125 -0
  24. package/dist/internal/bounds.cjs.map +1 -0
  25. package/dist/internal/bounds.d.cts +36 -0
  26. package/dist/internal/bounds.d.ts +36 -0
  27. package/dist/internal/bounds.mjs +81 -0
  28. package/dist/internal/bounds.mjs.map +1 -0
  29. package/dist/internal/cache/fs.cjs +217 -0
  30. package/dist/internal/cache/fs.cjs.map +1 -0
  31. package/dist/internal/cache/fs.d.cts +57 -0
  32. package/dist/internal/cache/fs.d.ts +57 -0
  33. package/dist/internal/cache/fs.mjs +179 -0
  34. package/dist/internal/cache/fs.mjs.map +1 -0
  35. package/dist/internal/cache/index.browser.cjs +1184 -0
  36. package/dist/internal/cache/index.browser.cjs.map +1 -0
  37. package/dist/internal/cache/index.browser.d.cts +20 -0
  38. package/dist/internal/cache/index.browser.d.ts +20 -0
  39. package/dist/internal/cache/index.browser.mjs +36 -0
  40. package/dist/internal/cache/index.browser.mjs.map +1 -0
  41. package/dist/internal/cache/index.cjs +1389 -0
  42. package/dist/internal/cache/index.cjs.map +1 -0
  43. package/dist/internal/cache/index.d.cts +16 -0
  44. package/dist/internal/cache/index.d.ts +16 -0
  45. package/dist/internal/cache/index.mjs +40 -0
  46. package/dist/internal/cache/index.mjs.map +1 -0
  47. package/dist/internal/chunk-PKJXHY27.mjs +1137 -0
  48. package/dist/internal/chunk-PKJXHY27.mjs.map +1 -0
  49. package/dist/internal/convert.cjs +161 -0
  50. package/dist/internal/convert.cjs.map +1 -0
  51. package/dist/internal/convert.d.cts +44 -0
  52. package/dist/internal/convert.d.ts +44 -0
  53. package/dist/internal/convert.mjs +117 -0
  54. package/dist/internal/convert.mjs.map +1 -0
  55. package/dist/internal/fs-O6XR4WWW.mjs +183 -0
  56. package/dist/internal/fs-O6XR4WWW.mjs.map +1 -0
  57. package/dist/internal/keys-B7C8C88N.d.cts +191 -0
  58. package/dist/internal/keys-B7C8C88N.d.ts +191 -0
  59. package/dist/internal/merge/index.cjs +75 -0
  60. package/dist/internal/merge/index.cjs.map +1 -0
  61. package/dist/internal/merge/index.d.cts +74 -0
  62. package/dist/internal/merge/index.d.ts +74 -0
  63. package/dist/internal/merge/index.mjs +46 -0
  64. package/dist/internal/merge/index.mjs.map +1 -0
  65. package/dist/internal/pairs.cjs +328 -0
  66. package/dist/internal/pairs.cjs.map +1 -0
  67. package/dist/internal/pairs.d.cts +105 -0
  68. package/dist/internal/pairs.d.ts +105 -0
  69. package/dist/internal/pairs.mjs +298 -0
  70. package/dist/internal/pairs.mjs.map +1 -0
  71. package/dist/qc/index.cjs +247 -0
  72. package/dist/qc/index.cjs.map +1 -0
  73. package/dist/qc/index.d.cts +140 -0
  74. package/dist/qc/index.d.ts +140 -0
  75. package/dist/qc/index.mjs +212 -0
  76. package/dist/qc/index.mjs.map +1 -0
  77. package/dist/temporal/index.cjs +504 -0
  78. package/dist/temporal/index.cjs.map +1 -0
  79. package/dist/temporal/index.d.cts +121 -0
  80. package/dist/temporal/index.d.ts +121 -0
  81. package/dist/temporal/index.mjs +474 -0
  82. package/dist/temporal/index.mjs.map +1 -0
  83. package/dist/transforms/index.cjs +399 -0
  84. package/dist/transforms/index.cjs.map +1 -0
  85. package/dist/transforms/index.d.cts +193 -0
  86. package/dist/transforms/index.d.ts +193 -0
  87. package/dist/transforms/index.mjs +362 -0
  88. package/dist/transforms/index.mjs.map +1 -0
  89. package/dist/validator.cjs +1870 -0
  90. package/dist/validator.cjs.map +1 -0
  91. package/dist/validator.d.cts +30 -0
  92. package/dist/validator.d.ts +30 -0
  93. package/dist/validator.mjs +1843 -0
  94. package/dist/validator.mjs.map +1 -0
  95. package/package.json +115 -0
@@ -0,0 +1,313 @@
1
+ /** Optional setters for cache writes. */
2
+ interface CacheSetOptions {
3
+ /** Time-to-live in milliseconds. Implementations may honor or ignore. */
4
+ readonly ttlMs?: number;
5
+ }
6
+ /**
7
+ * Pluggable key/value cache contract used throughout the SDK.
8
+ *
9
+ * All methods are async — concrete implementations may resolve immediately
10
+ * (MemoryStore) or do I/O (FsStore / IndexedDBStore).
11
+ *
12
+ * Semantic contract:
13
+ * - `get<T>(key)` returns the stored value or `null` on miss. NEVER throws
14
+ * on miss.
15
+ * - `set<T>(key, value, opts?)` overwrites. ttlMs is implementation-honored
16
+ * (MemoryStore + IndexedDBStore honor it; FsStore ignores in v0.1).
17
+ * - `delete(key)` is a no-op on miss; returns void.
18
+ * - `withLock<T>(key, fn)` runs `fn` under a key-scoped exclusive lock and
19
+ * releases on settle (resolve OR throw). Nested calls to the same key
20
+ * serialize; calls to different keys MAY run in parallel.
21
+ */
22
+ interface CacheStore {
23
+ get<T = unknown>(key: string): Promise<T | null>;
24
+ set<T = unknown>(key: string, value: T, opts?: CacheSetOptions): Promise<void>;
25
+ delete(key: string): Promise<void>;
26
+ withLock<T>(key: string, fn: () => Promise<T>): Promise<T>;
27
+ }
28
+
29
+ /**
30
+ * Cache-coverage summary for a station.
31
+ *
32
+ * Mirrors Python `availability()` return shape.
33
+ */
34
+ interface AvailabilityResult {
35
+ station: string;
36
+ /** Count of distinct (year, month) observation cache entries. */
37
+ monthsCached: number;
38
+ /** Earliest cached month as `"YYYY-MM"`, or null if none. */
39
+ firstMonth: string | null;
40
+ /** Latest cached month as `"YYYY-MM"`, or null if none. */
41
+ lastMonth: string | null;
42
+ /** Count of cached climate years. */
43
+ climateYears: number;
44
+ /** Earliest cached climate year as `"YYYY"`, or null if none. */
45
+ firstClimateYear: string | null;
46
+ /** Latest cached climate year as `"YYYY"`, or null if none. */
47
+ lastClimateYear: string | null;
48
+ }
49
+ /**
50
+ * Optional adapter that lets `availability()` enumerate keys from a store.
51
+ *
52
+ * CacheStore's mandatory contract is opaque key-value: get/set/delete/withLock.
53
+ * Discovery needs to enumerate which keys exist for a station — this is
54
+ * implementation-specific (Memory iterates its Map, IndexedDB uses
55
+ * `getAllKeys`, Fs walks the directory tree). Stores that support enumeration
56
+ * implement the optional `listKeys(prefix)` method and `availability()` uses
57
+ * it; stores without it return zero-coverage but never throw.
58
+ *
59
+ * Listed keys may exceed the requested prefix in the result (callers filter);
60
+ * `listKeys` is best-effort.
61
+ */
62
+ interface KeyEnumerableStore extends CacheStore {
63
+ listKeys(prefix: string): Promise<ReadonlyArray<string>>;
64
+ }
65
+ /**
66
+ * Options for `availability()`.
67
+ */
68
+ interface AvailabilityOptions {
69
+ /**
70
+ * If true, confirm each candidate key with `cache.get()` before counting.
71
+ * Eliminates the small overcount possible on stores whose `listKeys()` can
72
+ * return keys with already-expired TTL entries (FsStore and IndexedDBStore
73
+ * lazy-evict on `get`, not on `listKeys` — codex iter-3 P2). Off by default
74
+ * because the v0.1.0 `research()` flow never writes with `ttlMs`, so the
75
+ * overcount window is empty; turn on only if you populate the cache with
76
+ * explicit TTLs.
77
+ *
78
+ * Cost: one `get()` per matching key. On warm caches this is cheap
79
+ * (MemoryStore + IndexedDBStore in-memory). On FsStore it reads each
80
+ * candidate's file.
81
+ */
82
+ readonly validate?: boolean;
83
+ }
84
+ /**
85
+ * Return a summary of cached coverage for `station`.
86
+ *
87
+ * Stores without enumeration support return a zero-coverage result with the
88
+ * station name populated (counts all zero, dates null).
89
+ *
90
+ * Pass `{ validate: true }` to confirm each candidate key via `cache.get()`
91
+ * — needed if your callers populate the cache with `ttlMs` and might query
92
+ * after expiry. The v0.1.0 `research()` flow does not use `ttlMs`, so the
93
+ * default (fast scan, no validation) is correct for the canonical path.
94
+ */
95
+ declare function availability(station: string, cache: CacheStore, opts?: AvailabilityOptions): Promise<AvailabilityResult>;
96
+
97
+ /** Minimal row shape consumed by `internationalDailyExtremes`. */
98
+ interface InternationalRow {
99
+ /** ISO 8601 UTC instant. Must end with `Z` or include an offset. */
100
+ observed_at?: string | null;
101
+ /** Air temperature in degrees Celsius. */
102
+ temp_c?: number | null;
103
+ /** 1-hour precipitation total in millimeters. */
104
+ precip_mm_1h?: number | null;
105
+ /** Source identifier (preserved on the tmin/tmax aggregate). */
106
+ source?: string | null;
107
+ }
108
+ interface DailyExtreme {
109
+ /** Station-local calendar date as `YYYY-MM-DD`. */
110
+ localDate: string;
111
+ /** Count of rows with a parseable temp_c. */
112
+ nObs: number;
113
+ /** Min temperature in °C, or null on low coverage. */
114
+ tempMinC: number | null;
115
+ /** Max temperature in °C, or null on low coverage. */
116
+ tempMaxC: number | null;
117
+ /** Mean temperature in °C, or null on low coverage. */
118
+ tempMeanC: number | null;
119
+ /** Min temperature in °F, or null on low coverage. */
120
+ tempMinF: number | null;
121
+ /** Max temperature in °F, or null on low coverage. */
122
+ tempMaxF: number | null;
123
+ /** Total 1-hour precipitation across the local day, in mm. */
124
+ precipMm: number;
125
+ /** Source identifier of the row that produced tmin (or null on low coverage). */
126
+ sourceTmin: string | null;
127
+ /** Source identifier of the row that produced tmax (or null on low coverage). */
128
+ sourceTmax: string | null;
129
+ }
130
+ interface InternationalDailyExtremesOptions {
131
+ /** IANA timezone identifier for the station, e.g. `"Asia/Tokyo"`. Required. */
132
+ stationTz: string;
133
+ /**
134
+ * Decimal places for HALF_UP rounding. Defaults to 0 (whole °C) — the
135
+ * international convention. Pass `1` for US-station tenths.
136
+ */
137
+ precision?: number;
138
+ /**
139
+ * Minimum number of observations required for tmin/tmax/tmean to be
140
+ * populated. Defaults to 12 (the Python threshold). Tests can override.
141
+ */
142
+ minObs?: number;
143
+ }
144
+ /**
145
+ * Roll up observation rows to per-local-calendar-day temperature extremes.
146
+ *
147
+ * @param rows raw observation rows (any source). Rows without a parseable
148
+ * `observed_at` are dropped.
149
+ * @param opts `stationTz` is required. Optional `precision` (default 0;
150
+ * pass 1 for US-station tenths) and `minObs` (default 12).
151
+ *
152
+ * @returns one entry per local calendar day with at least one row. Days
153
+ * with fewer than `minObs` rows have temps set to null.
154
+ */
155
+ declare function internationalDailyExtremes(rows: ReadonlyArray<InternationalRow>, opts: InternationalDailyExtremesOptions): DailyExtreme[];
156
+
157
+ /** Immutable reproducibility token stamping a single research() call. */
158
+ interface DataVersion {
159
+ readonly sdkVersion: string;
160
+ readonly schemaIds: ReadonlyArray<string>;
161
+ readonly sources: ReadonlyArray<string>;
162
+ readonly codeSha: string;
163
+ readonly dataSha: string;
164
+ readonly token: string;
165
+ }
166
+ interface DataVersionComponents {
167
+ sdkVersion: string;
168
+ schemaIds: ReadonlyArray<string>;
169
+ sources: ReadonlyArray<string>;
170
+ codeSha: string;
171
+ dataSha: string;
172
+ }
173
+ /**
174
+ * Build a frozen DataVersion from explicit components.
175
+ *
176
+ * Mirrors Python `DataVersion.from_components`: sorts schemaIds + sources
177
+ * INTERNALLY before the canonical hash so input order does not affect the
178
+ * token, but preserves the caller's order on the returned object's
179
+ * `schemaIds` and `sources` arrays. Codex iter-5 P2: prior version sorted
180
+ * the stored arrays alphabetically too, which masked source-priority order
181
+ * (e.g. `awc.live, ghcnh, iem.archive, ...` instead of the canonical
182
+ * `iem.archive, iem.live, awc.live, ghcnh, nws.cli` precedence Python
183
+ * preserves on the tuple).
184
+ */
185
+ declare function dataVersionFromComponents(components: DataVersionComponents): Promise<DataVersion>;
186
+ /**
187
+ * Build a DataVersion for a research() call. Mirrors Python `DataVersion.for_research`:
188
+ * the codeSha encodes the call signature (`research:STATION:FROM:TO`) and dataSha
189
+ * is supplied by the caller (typically a cache fingerprint).
190
+ *
191
+ * The schema ids + source contract match the v0.1.0 Python SDK exactly so tokens
192
+ * computed in TS match tokens computed in Python for the same inputs.
193
+ */
194
+ declare function dataVersionForResearch(args: {
195
+ sdkVersion: string;
196
+ station: string;
197
+ fromDate: string;
198
+ toDate: string;
199
+ dataSha: string;
200
+ }): Promise<DataVersion>;
201
+
202
+ /** Frozen snapshot wrapper around row data + provenance. */
203
+ interface DataSnapshot {
204
+ /** ISO 8601 UTC instant when the snapshot was built (always ends with Z). */
205
+ readonly knowledgeTime: string;
206
+ /** Schema id the rows conform to. */
207
+ readonly schemaId: string;
208
+ /** Source identifier (e.g. `iem.archive`, `awc.live`). Snapshot-scoped. */
209
+ readonly source: string;
210
+ /** Row payload — opaque to this layer. Frozen. */
211
+ readonly rows: ReadonlyArray<Readonly<Record<string, unknown>>>;
212
+ /** Optional reproducibility token. */
213
+ readonly dataVersion: DataVersion | null;
214
+ /** Optional arbitrary metadata. JSON-safe-coerced on `toDict`. */
215
+ readonly metadata: Readonly<Record<string, unknown>>;
216
+ /** JSON-safe dict form. */
217
+ toDict(): Record<string, unknown>;
218
+ /** TOON-v3 tabular form (rows only — provenance lives in the dict form). */
219
+ toToon(): string;
220
+ }
221
+ interface BuildSnapshotOptions {
222
+ schemaId: string;
223
+ source: string;
224
+ rows: ReadonlyArray<Record<string, unknown>>;
225
+ knowledgeTime?: Date | string;
226
+ dataVersion?: DataVersion | null;
227
+ metadata?: Record<string, unknown>;
228
+ }
229
+ /**
230
+ * Build a frozen DataSnapshot.
231
+ *
232
+ * Throws RangeError on invalid `knowledgeTime`. Row payloads are shallow-
233
+ * cloned and frozen so callers can't mutate snapshot state post-build.
234
+ */
235
+ declare function buildSnapshot(opts: BuildSnapshotOptions): DataSnapshot;
236
+
237
+ interface TradewindsErrorOptions {
238
+ errorCode?: string;
239
+ source?: string | null;
240
+ requestId?: string | null;
241
+ }
242
+ /**
243
+ * Base class for all mostlyright structured errors.
244
+ *
245
+ * `errorCode` is a stable enum (e.g. "SOURCE_UNAVAILABLE") used by callers /
246
+ * agents to branch on without parsing message text. `source` is the source id
247
+ * involved (e.g. "iem.archive") when applicable, and `requestId` correlates a
248
+ * JSON-RPC / MCP request id when applicable.
249
+ */
250
+ declare class TradewindsError extends Error {
251
+ /** Subclass override — the stable string enum surfaced via `errorCode`. */
252
+ static defaultErrorCode: string;
253
+ readonly errorCode: string;
254
+ readonly source: string | null;
255
+ readonly requestId: string | null;
256
+ constructor(message?: string, options?: TradewindsErrorOptions);
257
+ /**
258
+ * Subclass hook returning the structured attributes for `toDict`.
259
+ * Values are passed through `toJsonSafe` by `toDict()`, so subclasses
260
+ * don't need to coerce values themselves.
261
+ */
262
+ protected payload(): Record<string, unknown>;
263
+ /** Return a JSON-safe dict suitable for MCP `error.data`. */
264
+ toDict(): Record<string, unknown>;
265
+ }
266
+
267
+ interface SchemaInfo {
268
+ readonly id: string;
269
+ readonly title: string;
270
+ readonly columnCount: number;
271
+ readonly columns: ReadonlyArray<{
272
+ readonly name: string;
273
+ readonly description: string;
274
+ readonly nullable: boolean;
275
+ }>;
276
+ }
277
+ /**
278
+ * Register or override a schema for `describe()`. Built-in v0.1.0 schemas
279
+ * are registered at module load (BUILT_IN_SCHEMAS); callers may add custom
280
+ * schemas or override built-ins (e.g. with richer descriptions).
281
+ */
282
+ declare function registerSchema(info: SchemaInfo): void;
283
+ /**
284
+ * Return a multi-line description of a registered schema.
285
+ *
286
+ * @throws TradewindsError if `schemaId` is not registered. The error code is
287
+ * `UNKNOWN_SCHEMA` so callers can distinguish from validation/IO errors.
288
+ */
289
+ declare function describe(schemaId: string): string;
290
+ /** Thrown by `describe` when `schemaId` is not registered. */
291
+ declare class UnknownSchemaError extends TradewindsError {
292
+ constructor(message: string);
293
+ static readonly defaultErrorCode = "UNKNOWN_SCHEMA";
294
+ }
295
+ /**
296
+ * Return the transforms surface as a stable-sorted list.
297
+ *
298
+ * Matches Python `feature_catalog()`'s ordering — alphabetical on the
299
+ * snake_case names, here the camelCase TS names sorted identically.
300
+ */
301
+ declare function featureCatalog(): ReadonlyArray<string>;
302
+ /**
303
+ * Climate-gap scanning is Python-only in v0.1.0 (TS has no climate-year
304
+ * parquet cache yet). Throws to match the documented contract.
305
+ */
306
+ declare function climateGaps(_station: string, _fromDate: string, _toDate: string): never;
307
+ /** Thrown by `climateGaps` until the TS climate cache lands. */
308
+ declare class ClimateGapsNotImplementedError extends TradewindsError {
309
+ constructor(message: string);
310
+ static readonly defaultErrorCode = "NOT_IMPLEMENTED";
311
+ }
312
+
313
+ export { type AvailabilityOptions, type AvailabilityResult, type BuildSnapshotOptions, ClimateGapsNotImplementedError, type DailyExtreme, type DataSnapshot, type DataVersion, type DataVersionComponents, type InternationalDailyExtremesOptions, type InternationalRow, type KeyEnumerableStore, UnknownSchemaError, availability, buildSnapshot, climateGaps, dataVersionForResearch, dataVersionFromComponents, describe, featureCatalog, internationalDailyExtremes, registerSchema };
@@ -0,0 +1,313 @@
1
+ /** Optional setters for cache writes. */
2
+ interface CacheSetOptions {
3
+ /** Time-to-live in milliseconds. Implementations may honor or ignore. */
4
+ readonly ttlMs?: number;
5
+ }
6
+ /**
7
+ * Pluggable key/value cache contract used throughout the SDK.
8
+ *
9
+ * All methods are async — concrete implementations may resolve immediately
10
+ * (MemoryStore) or do I/O (FsStore / IndexedDBStore).
11
+ *
12
+ * Semantic contract:
13
+ * - `get<T>(key)` returns the stored value or `null` on miss. NEVER throws
14
+ * on miss.
15
+ * - `set<T>(key, value, opts?)` overwrites. ttlMs is implementation-honored
16
+ * (MemoryStore + IndexedDBStore honor it; FsStore ignores in v0.1).
17
+ * - `delete(key)` is a no-op on miss; returns void.
18
+ * - `withLock<T>(key, fn)` runs `fn` under a key-scoped exclusive lock and
19
+ * releases on settle (resolve OR throw). Nested calls to the same key
20
+ * serialize; calls to different keys MAY run in parallel.
21
+ */
22
+ interface CacheStore {
23
+ get<T = unknown>(key: string): Promise<T | null>;
24
+ set<T = unknown>(key: string, value: T, opts?: CacheSetOptions): Promise<void>;
25
+ delete(key: string): Promise<void>;
26
+ withLock<T>(key: string, fn: () => Promise<T>): Promise<T>;
27
+ }
28
+
29
+ /**
30
+ * Cache-coverage summary for a station.
31
+ *
32
+ * Mirrors Python `availability()` return shape.
33
+ */
34
+ interface AvailabilityResult {
35
+ station: string;
36
+ /** Count of distinct (year, month) observation cache entries. */
37
+ monthsCached: number;
38
+ /** Earliest cached month as `"YYYY-MM"`, or null if none. */
39
+ firstMonth: string | null;
40
+ /** Latest cached month as `"YYYY-MM"`, or null if none. */
41
+ lastMonth: string | null;
42
+ /** Count of cached climate years. */
43
+ climateYears: number;
44
+ /** Earliest cached climate year as `"YYYY"`, or null if none. */
45
+ firstClimateYear: string | null;
46
+ /** Latest cached climate year as `"YYYY"`, or null if none. */
47
+ lastClimateYear: string | null;
48
+ }
49
+ /**
50
+ * Optional adapter that lets `availability()` enumerate keys from a store.
51
+ *
52
+ * CacheStore's mandatory contract is opaque key-value: get/set/delete/withLock.
53
+ * Discovery needs to enumerate which keys exist for a station — this is
54
+ * implementation-specific (Memory iterates its Map, IndexedDB uses
55
+ * `getAllKeys`, Fs walks the directory tree). Stores that support enumeration
56
+ * implement the optional `listKeys(prefix)` method and `availability()` uses
57
+ * it; stores without it return zero-coverage but never throw.
58
+ *
59
+ * Listed keys may exceed the requested prefix in the result (callers filter);
60
+ * `listKeys` is best-effort.
61
+ */
62
+ interface KeyEnumerableStore extends CacheStore {
63
+ listKeys(prefix: string): Promise<ReadonlyArray<string>>;
64
+ }
65
+ /**
66
+ * Options for `availability()`.
67
+ */
68
+ interface AvailabilityOptions {
69
+ /**
70
+ * If true, confirm each candidate key with `cache.get()` before counting.
71
+ * Eliminates the small overcount possible on stores whose `listKeys()` can
72
+ * return keys with already-expired TTL entries (FsStore and IndexedDBStore
73
+ * lazy-evict on `get`, not on `listKeys` — codex iter-3 P2). Off by default
74
+ * because the v0.1.0 `research()` flow never writes with `ttlMs`, so the
75
+ * overcount window is empty; turn on only if you populate the cache with
76
+ * explicit TTLs.
77
+ *
78
+ * Cost: one `get()` per matching key. On warm caches this is cheap
79
+ * (MemoryStore + IndexedDBStore in-memory). On FsStore it reads each
80
+ * candidate's file.
81
+ */
82
+ readonly validate?: boolean;
83
+ }
84
+ /**
85
+ * Return a summary of cached coverage for `station`.
86
+ *
87
+ * Stores without enumeration support return a zero-coverage result with the
88
+ * station name populated (counts all zero, dates null).
89
+ *
90
+ * Pass `{ validate: true }` to confirm each candidate key via `cache.get()`
91
+ * — needed if your callers populate the cache with `ttlMs` and might query
92
+ * after expiry. The v0.1.0 `research()` flow does not use `ttlMs`, so the
93
+ * default (fast scan, no validation) is correct for the canonical path.
94
+ */
95
+ declare function availability(station: string, cache: CacheStore, opts?: AvailabilityOptions): Promise<AvailabilityResult>;
96
+
97
+ /** Minimal row shape consumed by `internationalDailyExtremes`. */
98
+ interface InternationalRow {
99
+ /** ISO 8601 UTC instant. Must end with `Z` or include an offset. */
100
+ observed_at?: string | null;
101
+ /** Air temperature in degrees Celsius. */
102
+ temp_c?: number | null;
103
+ /** 1-hour precipitation total in millimeters. */
104
+ precip_mm_1h?: number | null;
105
+ /** Source identifier (preserved on the tmin/tmax aggregate). */
106
+ source?: string | null;
107
+ }
108
+ interface DailyExtreme {
109
+ /** Station-local calendar date as `YYYY-MM-DD`. */
110
+ localDate: string;
111
+ /** Count of rows with a parseable temp_c. */
112
+ nObs: number;
113
+ /** Min temperature in °C, or null on low coverage. */
114
+ tempMinC: number | null;
115
+ /** Max temperature in °C, or null on low coverage. */
116
+ tempMaxC: number | null;
117
+ /** Mean temperature in °C, or null on low coverage. */
118
+ tempMeanC: number | null;
119
+ /** Min temperature in °F, or null on low coverage. */
120
+ tempMinF: number | null;
121
+ /** Max temperature in °F, or null on low coverage. */
122
+ tempMaxF: number | null;
123
+ /** Total 1-hour precipitation across the local day, in mm. */
124
+ precipMm: number;
125
+ /** Source identifier of the row that produced tmin (or null on low coverage). */
126
+ sourceTmin: string | null;
127
+ /** Source identifier of the row that produced tmax (or null on low coverage). */
128
+ sourceTmax: string | null;
129
+ }
130
+ interface InternationalDailyExtremesOptions {
131
+ /** IANA timezone identifier for the station, e.g. `"Asia/Tokyo"`. Required. */
132
+ stationTz: string;
133
+ /**
134
+ * Decimal places for HALF_UP rounding. Defaults to 0 (whole °C) — the
135
+ * international convention. Pass `1` for US-station tenths.
136
+ */
137
+ precision?: number;
138
+ /**
139
+ * Minimum number of observations required for tmin/tmax/tmean to be
140
+ * populated. Defaults to 12 (the Python threshold). Tests can override.
141
+ */
142
+ minObs?: number;
143
+ }
144
+ /**
145
+ * Roll up observation rows to per-local-calendar-day temperature extremes.
146
+ *
147
+ * @param rows raw observation rows (any source). Rows without a parseable
148
+ * `observed_at` are dropped.
149
+ * @param opts `stationTz` is required. Optional `precision` (default 0;
150
+ * pass 1 for US-station tenths) and `minObs` (default 12).
151
+ *
152
+ * @returns one entry per local calendar day with at least one row. Days
153
+ * with fewer than `minObs` rows have temps set to null.
154
+ */
155
+ declare function internationalDailyExtremes(rows: ReadonlyArray<InternationalRow>, opts: InternationalDailyExtremesOptions): DailyExtreme[];
156
+
157
+ /** Immutable reproducibility token stamping a single research() call. */
158
+ interface DataVersion {
159
+ readonly sdkVersion: string;
160
+ readonly schemaIds: ReadonlyArray<string>;
161
+ readonly sources: ReadonlyArray<string>;
162
+ readonly codeSha: string;
163
+ readonly dataSha: string;
164
+ readonly token: string;
165
+ }
166
+ interface DataVersionComponents {
167
+ sdkVersion: string;
168
+ schemaIds: ReadonlyArray<string>;
169
+ sources: ReadonlyArray<string>;
170
+ codeSha: string;
171
+ dataSha: string;
172
+ }
173
+ /**
174
+ * Build a frozen DataVersion from explicit components.
175
+ *
176
+ * Mirrors Python `DataVersion.from_components`: sorts schemaIds + sources
177
+ * INTERNALLY before the canonical hash so input order does not affect the
178
+ * token, but preserves the caller's order on the returned object's
179
+ * `schemaIds` and `sources` arrays. Codex iter-5 P2: prior version sorted
180
+ * the stored arrays alphabetically too, which masked source-priority order
181
+ * (e.g. `awc.live, ghcnh, iem.archive, ...` instead of the canonical
182
+ * `iem.archive, iem.live, awc.live, ghcnh, nws.cli` precedence Python
183
+ * preserves on the tuple).
184
+ */
185
+ declare function dataVersionFromComponents(components: DataVersionComponents): Promise<DataVersion>;
186
+ /**
187
+ * Build a DataVersion for a research() call. Mirrors Python `DataVersion.for_research`:
188
+ * the codeSha encodes the call signature (`research:STATION:FROM:TO`) and dataSha
189
+ * is supplied by the caller (typically a cache fingerprint).
190
+ *
191
+ * The schema ids + source contract match the v0.1.0 Python SDK exactly so tokens
192
+ * computed in TS match tokens computed in Python for the same inputs.
193
+ */
194
+ declare function dataVersionForResearch(args: {
195
+ sdkVersion: string;
196
+ station: string;
197
+ fromDate: string;
198
+ toDate: string;
199
+ dataSha: string;
200
+ }): Promise<DataVersion>;
201
+
202
+ /** Frozen snapshot wrapper around row data + provenance. */
203
+ interface DataSnapshot {
204
+ /** ISO 8601 UTC instant when the snapshot was built (always ends with Z). */
205
+ readonly knowledgeTime: string;
206
+ /** Schema id the rows conform to. */
207
+ readonly schemaId: string;
208
+ /** Source identifier (e.g. `iem.archive`, `awc.live`). Snapshot-scoped. */
209
+ readonly source: string;
210
+ /** Row payload — opaque to this layer. Frozen. */
211
+ readonly rows: ReadonlyArray<Readonly<Record<string, unknown>>>;
212
+ /** Optional reproducibility token. */
213
+ readonly dataVersion: DataVersion | null;
214
+ /** Optional arbitrary metadata. JSON-safe-coerced on `toDict`. */
215
+ readonly metadata: Readonly<Record<string, unknown>>;
216
+ /** JSON-safe dict form. */
217
+ toDict(): Record<string, unknown>;
218
+ /** TOON-v3 tabular form (rows only — provenance lives in the dict form). */
219
+ toToon(): string;
220
+ }
221
+ interface BuildSnapshotOptions {
222
+ schemaId: string;
223
+ source: string;
224
+ rows: ReadonlyArray<Record<string, unknown>>;
225
+ knowledgeTime?: Date | string;
226
+ dataVersion?: DataVersion | null;
227
+ metadata?: Record<string, unknown>;
228
+ }
229
+ /**
230
+ * Build a frozen DataSnapshot.
231
+ *
232
+ * Throws RangeError on invalid `knowledgeTime`. Row payloads are shallow-
233
+ * cloned and frozen so callers can't mutate snapshot state post-build.
234
+ */
235
+ declare function buildSnapshot(opts: BuildSnapshotOptions): DataSnapshot;
236
+
237
+ interface TradewindsErrorOptions {
238
+ errorCode?: string;
239
+ source?: string | null;
240
+ requestId?: string | null;
241
+ }
242
+ /**
243
+ * Base class for all mostlyright structured errors.
244
+ *
245
+ * `errorCode` is a stable enum (e.g. "SOURCE_UNAVAILABLE") used by callers /
246
+ * agents to branch on without parsing message text. `source` is the source id
247
+ * involved (e.g. "iem.archive") when applicable, and `requestId` correlates a
248
+ * JSON-RPC / MCP request id when applicable.
249
+ */
250
+ declare class TradewindsError extends Error {
251
+ /** Subclass override — the stable string enum surfaced via `errorCode`. */
252
+ static defaultErrorCode: string;
253
+ readonly errorCode: string;
254
+ readonly source: string | null;
255
+ readonly requestId: string | null;
256
+ constructor(message?: string, options?: TradewindsErrorOptions);
257
+ /**
258
+ * Subclass hook returning the structured attributes for `toDict`.
259
+ * Values are passed through `toJsonSafe` by `toDict()`, so subclasses
260
+ * don't need to coerce values themselves.
261
+ */
262
+ protected payload(): Record<string, unknown>;
263
+ /** Return a JSON-safe dict suitable for MCP `error.data`. */
264
+ toDict(): Record<string, unknown>;
265
+ }
266
+
267
+ interface SchemaInfo {
268
+ readonly id: string;
269
+ readonly title: string;
270
+ readonly columnCount: number;
271
+ readonly columns: ReadonlyArray<{
272
+ readonly name: string;
273
+ readonly description: string;
274
+ readonly nullable: boolean;
275
+ }>;
276
+ }
277
+ /**
278
+ * Register or override a schema for `describe()`. Built-in v0.1.0 schemas
279
+ * are registered at module load (BUILT_IN_SCHEMAS); callers may add custom
280
+ * schemas or override built-ins (e.g. with richer descriptions).
281
+ */
282
+ declare function registerSchema(info: SchemaInfo): void;
283
+ /**
284
+ * Return a multi-line description of a registered schema.
285
+ *
286
+ * @throws TradewindsError if `schemaId` is not registered. The error code is
287
+ * `UNKNOWN_SCHEMA` so callers can distinguish from validation/IO errors.
288
+ */
289
+ declare function describe(schemaId: string): string;
290
+ /** Thrown by `describe` when `schemaId` is not registered. */
291
+ declare class UnknownSchemaError extends TradewindsError {
292
+ constructor(message: string);
293
+ static readonly defaultErrorCode = "UNKNOWN_SCHEMA";
294
+ }
295
+ /**
296
+ * Return the transforms surface as a stable-sorted list.
297
+ *
298
+ * Matches Python `feature_catalog()`'s ordering — alphabetical on the
299
+ * snake_case names, here the camelCase TS names sorted identically.
300
+ */
301
+ declare function featureCatalog(): ReadonlyArray<string>;
302
+ /**
303
+ * Climate-gap scanning is Python-only in v0.1.0 (TS has no climate-year
304
+ * parquet cache yet). Throws to match the documented contract.
305
+ */
306
+ declare function climateGaps(_station: string, _fromDate: string, _toDate: string): never;
307
+ /** Thrown by `climateGaps` until the TS climate cache lands. */
308
+ declare class ClimateGapsNotImplementedError extends TradewindsError {
309
+ constructor(message: string);
310
+ static readonly defaultErrorCode = "NOT_IMPLEMENTED";
311
+ }
312
+
313
+ export { type AvailabilityOptions, type AvailabilityResult, type BuildSnapshotOptions, ClimateGapsNotImplementedError, type DailyExtreme, type DataSnapshot, type DataVersion, type DataVersionComponents, type InternationalDailyExtremesOptions, type InternationalRow, type KeyEnumerableStore, UnknownSchemaError, availability, buildSnapshot, climateGaps, dataVersionForResearch, dataVersionFromComponents, describe, featureCatalog, internationalDailyExtremes, registerSchema };