mostlyrightmd 1.1.2__tar.gz → 1.2.0__tar.gz

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 (149) hide show
  1. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/PKG-INFO +11 -6
  2. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/README.md +3 -3
  3. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/pyproject.toml +29 -3
  4. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/exceptions.py +89 -0
  5. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/forecasts.py +12 -6
  6. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/research.py +56 -0
  7. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_compose.py +4 -1
  8. mostlyrightmd-1.2.0/tests/test_exceptions_data_availability.py +82 -0
  9. mostlyrightmd-1.2.0/tests/test_research_kwarg_validation.py +81 -0
  10. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/.gitignore +0 -0
  11. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/__init__.py +0 -0
  12. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_compose.py +0 -0
  13. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_exact_fetch.py +0 -0
  14. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/__init__.py +0 -0
  15. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/_bounds.py +0 -0
  16. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/_cache_dir.py +0 -0
  17. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/_capabilities.py +0 -0
  18. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/_convert.py +0 -0
  19. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/_http.py +0 -0
  20. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/_pairs.py +0 -0
  21. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/_pandas_compat.py +0 -0
  22. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/_stations.py +0 -0
  23. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/_toon.py +0 -0
  24. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/exceptions.py +0 -0
  25. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/merge/__init__.py +0 -0
  26. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/merge/_schemas.py +0 -0
  27. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/merge/climate.py +0 -0
  28. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/merge/observations.py +0 -0
  29. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/models/__init__.py +0 -0
  30. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/models/_base.py +0 -0
  31. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/models/availability.py +0 -0
  32. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/models/observation.py +0 -0
  33. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/models/station.py +0 -0
  34. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/book_snapshot.json +0 -0
  35. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/brackets.json +0 -0
  36. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/candle.json +0 -0
  37. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/climate.json +0 -0
  38. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/daily_extreme.json +0 -0
  39. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/data_version.json +0 -0
  40. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/event.json +0 -0
  41. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/forecast.json +0 -0
  42. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/forecast_series.json +0 -0
  43. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/market.json +0 -0
  44. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/market_unified.json +0 -0
  45. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/observation.json +0 -0
  46. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/observation_ledger.json +0 -0
  47. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/observation_qc.json +0 -0
  48. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/omo.json +0 -0
  49. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/series.json +0 -0
  50. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/settlement-join.json +0 -0
  51. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/settlement_record.json +0 -0
  52. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/snapshot.json +0 -0
  53. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/specs/synoptic_extremes.json +0 -0
  54. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/_internal/versioning.py +0 -0
  55. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/__init__.py +0 -0
  56. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/_backend_dispatch.py +0 -0
  57. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/_json_safe.py +0 -0
  58. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/_narwhals_compat.py +0 -0
  59. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/_polars_compat.py +0 -0
  60. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/formats/__init__.py +0 -0
  61. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/formats/_toon.py +0 -0
  62. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/formats/_toon_list_codec.py +0 -0
  63. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/formats/csv.py +0 -0
  64. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/formats/dataframe.py +0 -0
  65. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/formats/json.py +0 -0
  66. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/formats/parquet.py +0 -0
  67. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/formats/toon.py +0 -0
  68. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/merge.py +0 -0
  69. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/result.py +0 -0
  70. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/schema.py +0 -0
  71. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/schemas/__init__.py +0 -0
  72. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/schemas/forecast.py +0 -0
  73. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/schemas/forecast_nwp.py +0 -0
  74. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/schemas/observation.py +0 -0
  75. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/schemas/observation_ledger.py +0 -0
  76. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/schemas/observation_qc.py +0 -0
  77. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/schemas/settlement.py +0 -0
  78. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/temporal/__init__.py +0 -0
  79. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/temporal/knowledge_view.py +0 -0
  80. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/temporal/leakage.py +0 -0
  81. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/temporal/timepoint.py +0 -0
  82. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/core/validator.py +0 -0
  83. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/discover.py +0 -0
  84. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/discovery.py +0 -0
  85. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/international.py +0 -0
  86. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/live/__init__.py +0 -0
  87. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/live/_latest.py +0 -0
  88. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/live/_sources.py +0 -0
  89. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/live/_stream.py +0 -0
  90. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/mode2.py +0 -0
  91. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/preprocessing.py +0 -0
  92. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/qc.py +0 -0
  93. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/snapshot.py +0 -0
  94. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/src/mostlyright/transforms.py +0 -0
  95. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/__init__.py +0 -0
  96. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/merge/__init__.py +0 -0
  97. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/merge/test_awc_gap_filled_by_iem.py +0 -0
  98. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/merge/test_climate.py +0 -0
  99. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/merge/test_observations.py +0 -0
  100. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/models/__init__.py +0 -0
  101. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/models/test_availability.py +0 -0
  102. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/models/test_base.py +0 -0
  103. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/models/test_observation.py +0 -0
  104. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/models/test_station.py +0 -0
  105. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/test_bounds.py +0 -0
  106. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/test_capabilities.py +0 -0
  107. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/test_convert.py +0 -0
  108. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/test_exceptions.py +0 -0
  109. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/test_http.py +0 -0
  110. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/test_pairs.py +0 -0
  111. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/test_stations.py +0 -0
  112. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/_internal/test_versioning.py +0 -0
  113. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/__init__.py +0 -0
  114. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/temporal/__init__.py +0 -0
  115. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/temporal/test_knowledge_view.py +0 -0
  116. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/temporal/test_leakage.py +0 -0
  117. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_exceptions.py +0 -0
  118. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_formats.py +0 -0
  119. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_json_safe.py +0 -0
  120. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_merge.py +0 -0
  121. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_result.py +0 -0
  122. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_schema.py +0 -0
  123. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_schemas/__init__.py +0 -0
  124. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_schemas/test_forecast.py +0 -0
  125. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_schemas/test_observation.py +0 -0
  126. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_schemas/test_settlement.py +0 -0
  127. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_timepoint.py +0 -0
  128. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/core/test_validator.py +0 -0
  129. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_backend_dispatch.py +0 -0
  130. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_cache_env_back_compat.py +0 -0
  131. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_discover.py +0 -0
  132. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_discovery_real.py +0 -0
  133. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_exact_fetch.py +0 -0
  134. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_exceptions_phase17.py +0 -0
  135. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_exceptions_phase17_plan06.py +0 -0
  136. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_forecast_nwp_schema_phase17.py +0 -0
  137. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_international.py +0 -0
  138. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_live_latest.py +0 -0
  139. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_live_stream.py +0 -0
  140. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_mode2.py +0 -0
  141. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_namespace.py +0 -0
  142. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_phase_3x.py +0 -0
  143. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_polars_cross_backend.py +0 -0
  144. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_polars_invariants.py +0 -0
  145. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_qc_wired.py +0 -0
  146. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_research.py +0 -0
  147. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_research_prefetch.py +0 -0
  148. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_snapshot.py +0 -0
  149. {mostlyrightmd-1.1.2 → mostlyrightmd-1.2.0}/tests/test_transforms_preprocessing.py +0 -0
@@ -1,10 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mostlyrightmd
3
- Version: 1.1.2
4
- Summary: Local-first Python SDK for prediction-market weather research. Calls AWC, IEM, GHCNh, and NWS CLI directly; no hosted backend. Exposes research(station, from_date, to_date) for joined observation + climate rows. Imports as `mostlyright`.
3
+ Version: 1.2.0
4
+ Summary: Python SDK for quants, ML engineers, and AI agents — one interface to public data. Adapters ship weather + prediction-market settlements (Kalshi NHIGH/NLOW, Polymarket) today; SEC filings, Federal Reserve series, court filings, FDA approvals, and equities are next. Schema-versioned, leakage-free, local-first. Imports as `mostlyright`.
5
+ Project-URL: Homepage, https://mostlyright.md
6
+ Project-URL: Documentation, https://mostlyright.md/docs/sdk/
7
+ Project-URL: Repository, https://github.com/mostlyrightmd/mostlyright-sdk
8
+ Project-URL: Issues, https://github.com/mostlyrightmd/mostlyright-sdk/issues
9
+ Project-URL: Changelog, https://github.com/mostlyrightmd/mostlyright-sdk/blob/main/CHANGELOG.md
5
10
  Author-email: Robert Tarabcak <tarabcakr@gmail.com>
6
11
  License-Expression: MIT
7
- Keywords: kalshi,metar,polymarket,quantitative-finance,weather
12
+ Keywords: ai-agents,asos,backtesting,kalshi,local-first,machine-learning,metar,ml-training,nws,polymarket,prediction-markets,quant-research,quantitative-finance,schema-versioned,time-series,weather,weather-data
8
13
  Classifier: Development Status :: 2 - Pre-Alpha
9
14
  Classifier: Intended Audience :: Financial and Insurance Industry
10
15
  Classifier: License :: OSI Approved :: MIT License
@@ -31,11 +36,11 @@ Description-Content-Type: text/markdown
31
36
 
32
37
  # mostlyrightmd
33
38
 
34
- **The public data SDK for quants and AI agents.**
39
+ **The public-data SDK for quants, ML pipelines, and AI agents.**
35
40
 
36
- `mostlyrightmd` is the Python entry point: core types, schemas, validators, temporal-safety primitives, and the `research()` join that ties observations × climate into one row per settlement date. Direct calls to public APIs (NOAA, NWS, IEM, GHCNh, Kalshi, Polymarket). No hosted backend, no API key.
41
+ `mostlyrightmd` is the Python entry point: core types, schemas, validators, temporal-safety primitives, and the `research()` join that ties weather observations × climate into one row per settlement date — ready for prediction-market backtests (Kalshi NHIGH/NLOW, Polymarket), ML training pipelines, and AI-agent tool calls. Direct calls to public APIs (NOAA, NWS, IEM, GHCNh, Kalshi, Polymarket). No hosted backend, no API key.
37
42
 
38
- Weather + prediction-markets data are live today. SEC filings (EDGAR) and Federal Reserve economic data (FRED) are next.
43
+ Weather + prediction-markets adapters are live today. SEC filings (EDGAR), equities structured data, Federal Reserve series (FRED), court filings, and FDA approvals are next — and the architecture is built to ship an adapter for any public data source.
39
44
 
40
45
  ## Install
41
46
 
@@ -1,10 +1,10 @@
1
1
  # mostlyrightmd
2
2
 
3
- **The public data SDK for quants and AI agents.**
3
+ **The public-data SDK for quants, ML pipelines, and AI agents.**
4
4
 
5
- `mostlyrightmd` is the Python entry point: core types, schemas, validators, temporal-safety primitives, and the `research()` join that ties observations × climate into one row per settlement date. Direct calls to public APIs (NOAA, NWS, IEM, GHCNh, Kalshi, Polymarket). No hosted backend, no API key.
5
+ `mostlyrightmd` is the Python entry point: core types, schemas, validators, temporal-safety primitives, and the `research()` join that ties weather observations × climate into one row per settlement date — ready for prediction-market backtests (Kalshi NHIGH/NLOW, Polymarket), ML training pipelines, and AI-agent tool calls. Direct calls to public APIs (NOAA, NWS, IEM, GHCNh, Kalshi, Polymarket). No hosted backend, no API key.
6
6
 
7
- Weather + prediction-markets data are live today. SEC filings (EDGAR) and Federal Reserve economic data (FRED) are next.
7
+ Weather + prediction-markets adapters are live today. SEC filings (EDGAR), equities structured data, Federal Reserve series (FRED), court filings, and FDA approvals are next — and the architecture is built to ship an adapter for any public data source.
8
8
 
9
9
  ## Install
10
10
 
@@ -1,12 +1,30 @@
1
1
  [project]
2
2
  name = "mostlyrightmd"
3
- version = "1.1.2"
4
- description = "Local-first Python SDK for prediction-market weather research. Calls AWC, IEM, GHCNh, and NWS CLI directly; no hosted backend. Exposes research(station, from_date, to_date) for joined observation + climate rows. Imports as `mostlyright`."
3
+ version = "1.2.0"
4
+ description = "Python SDK for quants, ML engineers, and AI agents — one interface to public data. Adapters ship weather + prediction-market settlements (Kalshi NHIGH/NLOW, Polymarket) today; SEC filings, Federal Reserve series, court filings, FDA approvals, and equities are next. Schema-versioned, leakage-free, local-first. Imports as `mostlyright`."
5
5
  readme = "README.md"
6
6
  license = "MIT"
7
7
  authors = [{ name = "Robert Tarabcak", email = "tarabcakr@gmail.com" }]
8
8
  requires-python = ">=3.11"
9
- keywords = ["weather", "quantitative-finance", "kalshi", "polymarket", "metar"]
9
+ keywords = [
10
+ "weather",
11
+ "weather-data",
12
+ "prediction-markets",
13
+ "kalshi",
14
+ "polymarket",
15
+ "machine-learning",
16
+ "ml-training",
17
+ "ai-agents",
18
+ "backtesting",
19
+ "quant-research",
20
+ "quantitative-finance",
21
+ "metar",
22
+ "asos",
23
+ "nws",
24
+ "time-series",
25
+ "schema-versioned",
26
+ "local-first",
27
+ ]
10
28
  classifiers = [
11
29
  "Development Status :: 2 - Pre-Alpha",
12
30
  "Intended Audience :: Financial and Insurance Industry",
@@ -15,6 +33,7 @@ classifiers = [
15
33
  "Programming Language :: Python :: 3.12",
16
34
  "Programming Language :: Python :: 3.13",
17
35
  ]
36
+
18
37
  dependencies = [
19
38
  # Pinned to v0.14.1 mostlyright deps for inherited test coverage (per TODOS.md item 1)
20
39
  "httpx>=0.27",
@@ -27,6 +46,13 @@ dependencies = [
27
46
  # (or `mostlyrightmd-weather` directly, which depends on core transitively).
28
47
  ]
29
48
 
49
+ [project.urls]
50
+ Homepage = "https://mostlyright.md"
51
+ Documentation = "https://mostlyright.md/docs/sdk/"
52
+ Repository = "https://github.com/mostlyrightmd/mostlyright-sdk"
53
+ Issues = "https://github.com/mostlyrightmd/mostlyright-sdk/issues"
54
+ Changelog = "https://github.com/mostlyrightmd/mostlyright-sdk/blob/main/CHANGELOG.md"
55
+
30
56
  [project.optional-dependencies]
31
57
  # DataFrame return + parquet cache. Matches v0.14.1's [parquet] extra.
32
58
  # Phase 6 (PANDAS3-02): the pandas<3.0 cap is dropped. Pandas 3 byte-
@@ -25,6 +25,9 @@ from typing import Any
25
25
  from ._json_safe import to_json_safe
26
26
 
27
27
  __all__ = [
28
+ "DATA_AVAILABILITY_REASONS",
29
+ "DataAvailabilityError",
30
+ "DataAvailabilityReason",
28
31
  "DeprecatedModelWarning",
29
32
  "GribIntegrityError",
30
33
  "HistoricalDepthError",
@@ -48,6 +51,26 @@ __all__ = [
48
51
  ]
49
52
 
50
53
 
54
+ #: Shared reason enum — MUST match TS lockstep (Phase 21 D-04). The TS
55
+ #: exception in ``packages-ts/core/src/exceptions/index.ts`` defines an
56
+ #: identical array under ``DATA_AVAILABILITY_REASONS``. Drift here invalidates
57
+ #: every cross-SDK ``except DataAvailabilityError as e: if e.reason == ...``
58
+ #: branch a consumer writes.
59
+ DATA_AVAILABILITY_REASONS: tuple[str, ...] = (
60
+ "model_unavailable",
61
+ "out_of_window",
62
+ "cache_miss",
63
+ "source_404",
64
+ "source_5xx",
65
+ "rate_limited",
66
+ )
67
+
68
+ #: Type alias kept loose (``str``) rather than ``typing.Literal`` so callers
69
+ #: passing a string variable don't need to ``cast()``. Construction-time
70
+ #: validation in ``DataAvailabilityError.__init__`` enforces the enum.
71
+ DataAvailabilityReason = str
72
+
73
+
51
74
  class TradewindsError(Exception):
52
75
  """Base class for all mostlyright structured errors.
53
76
 
@@ -141,6 +164,72 @@ class SourceUnavailableError(TradewindsError):
141
164
  return payload
142
165
 
143
166
 
167
+ class DataAvailabilityError(TradewindsError):
168
+ """Typed exception for "I tried to fetch and got nothing usable".
169
+
170
+ Phase 21 21-09 (Issue #26): replaces overloaded ``SourceUnavailableError``
171
+ raise sites across at least 3 paths (forecast model unavailable, AWC
172
+ outside 168h, IEM cache miss, fetcher 404/5xx, rate-limit). Consumers
173
+ branch on ``reason`` instead of string-matching ``message``:
174
+
175
+ try:
176
+ df = tw.research(...)
177
+ except DataAvailabilityError as e:
178
+ if e.reason == "out_of_window":
179
+ ... # widen the window
180
+ elif e.reason == "rate_limited":
181
+ ... # back off
182
+ else:
183
+ raise
184
+
185
+ The reason enum is shared lockstep with the TypeScript SDK — see
186
+ ``DATA_AVAILABILITY_REASONS`` and the matching const array in
187
+ ``packages-ts/core/src/exceptions/index.ts``. Drift between SDKs is the
188
+ load-bearing risk.
189
+
190
+ ``SourceUnavailableError`` remains in place for back-compat; new code
191
+ prefers ``DataAvailabilityError``.
192
+ """
193
+
194
+ default_error_code = "DATA_AVAILABILITY"
195
+
196
+ def __init__(
197
+ self,
198
+ message: str = "",
199
+ *,
200
+ reason: str,
201
+ hint: str,
202
+ source: str | None = None,
203
+ request_id: str | None = None,
204
+ error_code: str | None = None,
205
+ ) -> None:
206
+ if reason not in DATA_AVAILABILITY_REASONS:
207
+ valid = ", ".join(DATA_AVAILABILITY_REASONS)
208
+ raise ValueError(
209
+ f"DataAvailabilityError: unknown reason {reason!r}. Valid: {valid}",
210
+ )
211
+ if not isinstance(hint, str) or not hint:
212
+ raise TypeError(
213
+ "DataAvailabilityError: hint is required and must be a non-empty string",
214
+ )
215
+ super().__init__(
216
+ message or f"[{reason}] {hint}",
217
+ error_code=error_code,
218
+ source=source,
219
+ request_id=request_id,
220
+ )
221
+ self.reason: str = reason
222
+ self.hint: str = hint
223
+
224
+ def _payload(self) -> dict[str, Any]:
225
+ payload = super()._payload()
226
+ payload.update(
227
+ reason=self.reason,
228
+ hint=self.hint,
229
+ )
230
+ return payload
231
+
232
+
144
233
  class SchemaValidationError(TradewindsError):
145
234
  """A DataFrame failed schema validation. Carries the full violation list
146
235
  (capped at 10,000 — surplus written to file via §Q file-path mode by the
@@ -32,7 +32,6 @@ from typing import TYPE_CHECKING
32
32
  from mostlyright.core.exceptions import (
33
33
  DeprecatedModelWarning,
34
34
  NwpModelNotAvailableError,
35
- SourceUnavailableError,
36
35
  )
37
36
  from mostlyright.core.schemas.forecast_nwp import NWP_MODEL_VALUES
38
37
 
@@ -247,12 +246,19 @@ def forecast_nwp(
247
246
  forecast_nwp as _impl,
248
247
  )
249
248
  except ImportError as exc:
250
- raise SourceUnavailableError(
251
- f"mostlyright.weather.forecast_nwp is not available: {exc}. "
252
- "Install mostlyrightmd-weather: pip install mostlyrightmd-weather",
249
+ # Phase 21 21-09: migrated from SourceUnavailableError to the structural
250
+ # DataAvailabilityError so consumers can dispatch on `e.reason ==
251
+ # "model_unavailable"` rather than parsing the install hint out of the
252
+ # message. The hint is still operator-actionable.
253
+ from mostlyright.core.exceptions import DataAvailabilityError
254
+
255
+ raise DataAvailabilityError(
256
+ reason="model_unavailable",
257
+ hint=(
258
+ f"mostlyright.weather.forecast_nwp is not available: {exc}. "
259
+ "Install mostlyrightmd-weather: pip install mostlyrightmd-weather"
260
+ ),
253
261
  source=f"nwp.{model}",
254
- retryable=False,
255
- underlying=str(exc),
256
262
  ) from None
257
263
 
258
264
  return _impl(
@@ -1336,6 +1336,49 @@ def _run_qc_and_write_sidecar(
1336
1336
  return summary
1337
1337
 
1338
1338
 
1339
+ def _validate_research_kwargs(
1340
+ *,
1341
+ sources: object,
1342
+ source: object,
1343
+ forecast_model: object,
1344
+ forecast_models: object,
1345
+ include_forecast: bool,
1346
+ ) -> None:
1347
+ """Phase 21 21-01: tighten kwarg validation on `research()`.
1348
+
1349
+ Raises ``TypeError`` early (before any network fetch) when:
1350
+
1351
+ 1. Both ``sources=`` and ``source=`` are provided (mutually exclusive —
1352
+ ``sources`` is the LIVE_V1 plural multi-source selector; ``source``
1353
+ is the single-source filter for exact_window).
1354
+ 2. Both ``forecast_model=`` and ``forecast_models=`` are provided
1355
+ (mutually exclusive — ``forecast_models`` is the plural multi-model
1356
+ form; supplying both is ambiguous).
1357
+ 3. A forecast model is requested but ``include_forecast=False`` (silent
1358
+ no-op would mislead the caller into thinking forecast attached).
1359
+
1360
+ These guards match the TS ``ResearchOptions`` runtime checks lockstep
1361
+ so a cross-SDK consumer sees the same TypeError shape on either side.
1362
+ """
1363
+ if sources is not None and source is not None:
1364
+ raise TypeError(
1365
+ "research(): sources= and source= are mutually exclusive — "
1366
+ "use `sources=` for the LIVE_V1 multi-source selector or "
1367
+ "`source=` for a single-source exact_window query, not both",
1368
+ )
1369
+ if forecast_model is not None and forecast_models is not None:
1370
+ raise TypeError(
1371
+ "research(): forecast_model= and forecast_models= are mutually exclusive — "
1372
+ "use `forecast_models=` for multi-model fan-out or `forecast_model=` for "
1373
+ "a single model, not both",
1374
+ )
1375
+ if (forecast_model is not None or forecast_models is not None) and not include_forecast:
1376
+ raise TypeError(
1377
+ "research(): forecast_model=/forecast_models= require include_forecast=True; "
1378
+ "the model filter is otherwise silently ignored",
1379
+ )
1380
+
1381
+
1339
1382
  def research(
1340
1383
  station: str | None = None,
1341
1384
  from_date: str | None = None,
@@ -1425,6 +1468,19 @@ def research(
1425
1468
  >>> list(df.columns)[:4] # doctest: +SKIP
1426
1469
  ['station', 'cli_high_f', 'cli_low_f', 'cli_report_type']
1427
1470
  """
1471
+ # Phase 21 21-01: tighten kwarg validation BEFORE any backend dispatch.
1472
+ # Surfaces mutually-exclusive misuses (sources/source, forecast_model/
1473
+ # forecast_models) and silent-no-op cases (forecast_model without
1474
+ # include_forecast=True) up-front. Matches the TS `ResearchOptions`
1475
+ # runtime checks lockstep.
1476
+ _validate_research_kwargs(
1477
+ sources=sources,
1478
+ source=source,
1479
+ forecast_model=forecast_model,
1480
+ forecast_models=forecast_models,
1481
+ include_forecast=include_forecast,
1482
+ )
1483
+
1428
1484
  # Phase 6 codex iter-2 P2 fix: validate backend / return_type kwargs
1429
1485
  # BEFORE any network fetch or cache write. A typo in the new kwargs
1430
1486
  # otherwise hits live APIs + mutates the parquet cache before raising.
@@ -275,7 +275,10 @@ class TestResearchSignatureValidation:
275
275
  def test_sources_and_source_mutually_exclusive(self):
276
276
  from mostlyright import research
277
277
 
278
- with pytest.raises(ValueError, match="mutually exclusive"):
278
+ # Phase 21 21-01 tightened the validation to raise TypeError (matches
279
+ # the TS lockstep contract); pre-21-01 raised ValueError. Accept
280
+ # either to keep the test resilient if 21-01 reverts.
281
+ with pytest.raises((TypeError, ValueError), match="mutually exclusive"):
279
282
  research(
280
283
  station="NYC",
281
284
  from_date="2025-01-06",
@@ -0,0 +1,82 @@
1
+ """Phase 21 21-09: DataAvailabilityError typed exception.
2
+
3
+ Tests follow the existing exception pattern in mostlyright.core.exceptions:
4
+ ``to_dict()`` returns a JSON-safe payload keyed on ``error_code`` /
5
+ ``message`` / ``source`` / per-subclass fields. The ``reason`` enum is
6
+ shared lockstep with the TypeScript SDK — drift would invalidate every
7
+ cross-SDK ``except DataAvailabilityError as e: if e.reason == ...`` branch.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+
14
+ import pytest
15
+ from mostlyright.core import exceptions as exc_mod
16
+ from mostlyright.core.exceptions import (
17
+ DATA_AVAILABILITY_REASONS,
18
+ DataAvailabilityError,
19
+ TradewindsError,
20
+ )
21
+
22
+
23
+ def test_data_availability_error_constructs_with_all_fields() -> None:
24
+ exc = DataAvailabilityError(
25
+ reason="out_of_window",
26
+ hint="AWC only serves last 168h",
27
+ source="awc",
28
+ )
29
+ assert exc.reason == "out_of_window"
30
+ assert exc.hint == "AWC only serves last 168h"
31
+ assert exc.source == "awc"
32
+
33
+
34
+ def test_data_availability_error_to_dict_payload() -> None:
35
+ exc = DataAvailabilityError(
36
+ reason="model_unavailable",
37
+ hint="hosted ingest API ships in v0.2.x",
38
+ source="nwp-stub",
39
+ )
40
+ d = exc.to_dict()
41
+ assert d["error_code"] == "DATA_AVAILABILITY"
42
+ assert d["reason"] == "model_unavailable"
43
+ assert d["hint"] == "hosted ingest API ships in v0.2.x"
44
+ assert d["source"] == "nwp-stub"
45
+ # Default message derives from reason + hint when not explicitly passed.
46
+ assert d["message"] == "[model_unavailable] hosted ingest API ships in v0.2.x"
47
+ # JSON round-trip safe.
48
+ assert json.loads(json.dumps(d))["reason"] == "model_unavailable"
49
+
50
+
51
+ def test_data_availability_error_is_tradewinds_subclass() -> None:
52
+ """Back-compat: code that catches TradewindsError catches this too."""
53
+ exc = DataAvailabilityError(reason="rate_limited", hint="back off")
54
+ assert isinstance(exc, TradewindsError)
55
+ assert isinstance(exc, Exception)
56
+
57
+
58
+ def test_data_availability_error_rejects_unknown_reason() -> None:
59
+ with pytest.raises(ValueError, match="unknown reason"):
60
+ DataAvailabilityError(reason="totally_made_up", hint="x")
61
+
62
+
63
+ def test_data_availability_error_defaults_source_to_none() -> None:
64
+ exc = DataAvailabilityError(reason="cache_miss", hint="no cached value")
65
+ assert exc.source is None
66
+ # Empty hint rejected.
67
+ with pytest.raises(TypeError, match="hint is required"):
68
+ DataAvailabilityError(reason="cache_miss", hint="")
69
+
70
+
71
+ def test_data_availability_error_exported_in_all() -> None:
72
+ assert "DataAvailabilityError" in exc_mod.__all__
73
+ assert "DATA_AVAILABILITY_REASONS" in exc_mod.__all__
74
+ # Reason enum matches TS lockstep — drift here invalidates cross-SDK branches.
75
+ assert DATA_AVAILABILITY_REASONS == (
76
+ "model_unavailable",
77
+ "out_of_window",
78
+ "cache_miss",
79
+ "source_404",
80
+ "source_5xx",
81
+ "rate_limited",
82
+ )
@@ -0,0 +1,81 @@
1
+ """Phase 21 21-01: tighter kwarg validation on `research()`.
2
+
3
+ Asserts that mutually-exclusive misuses and silent-no-op cases raise
4
+ `TypeError` BEFORE any network fetch. Matches the TS `ResearchOptions`
5
+ runtime checks lockstep — cross-SDK consumers see the same shape on
6
+ either side.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import pytest
12
+ from mostlyright import research
13
+
14
+
15
+ def test_sources_and_source_mutually_exclusive() -> None:
16
+ with pytest.raises(TypeError, match="mutually exclusive"):
17
+ research(
18
+ "KNYC",
19
+ "2025-01-06",
20
+ "2025-01-12",
21
+ sources=["awc"],
22
+ source="iem",
23
+ )
24
+
25
+
26
+ def test_forecast_model_and_forecast_models_mutually_exclusive() -> None:
27
+ with pytest.raises(TypeError, match="mutually exclusive"):
28
+ research(
29
+ "KNYC",
30
+ "2025-01-06",
31
+ "2025-01-12",
32
+ include_forecast=True,
33
+ forecast_model="gfs",
34
+ forecast_models=["gfs", "nbm"],
35
+ )
36
+
37
+
38
+ def test_forecast_model_requires_include_forecast() -> None:
39
+ with pytest.raises(TypeError, match="include_forecast=True"):
40
+ research(
41
+ "KNYC",
42
+ "2025-01-06",
43
+ "2025-01-12",
44
+ forecast_model="gfs",
45
+ )
46
+
47
+
48
+ def test_forecast_models_requires_include_forecast() -> None:
49
+ with pytest.raises(TypeError, match="include_forecast=True"):
50
+ research(
51
+ "KNYC",
52
+ "2025-01-06",
53
+ "2025-01-12",
54
+ forecast_models=["gfs"],
55
+ )
56
+
57
+
58
+ def test_validator_fires_before_network_fetch(monkeypatch) -> None:
59
+ """The validator must run BEFORE any HTTP or cache I/O — a typo in
60
+ kwargs should not first hit AWC / IEM / GHCNh.
61
+
62
+ We assert this by stubbing the backend-dispatch validator to raise a
63
+ sentinel exception. If the kwarg validator runs first, we see TypeError;
64
+ if it runs after the backend validator, we see RuntimeError.
65
+ """
66
+
67
+ def _sentinel(_backend, _return_type):
68
+ raise RuntimeError("backend validator ran too early")
69
+
70
+ monkeypatch.setattr(
71
+ "mostlyright.core._backend_dispatch.validate_backend_kwargs",
72
+ _sentinel,
73
+ )
74
+ with pytest.raises(TypeError):
75
+ research(
76
+ "KNYC",
77
+ "2025-01-06",
78
+ "2025-01-12",
79
+ sources=["awc"],
80
+ source="iem",
81
+ )
File without changes