xrtm-data 0.3.2__tar.gz → 0.5.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.
- {xrtm_data-0.3.2/src/xrtm_data.egg-info → xrtm_data-0.5.0}/PKG-INFO +2 -2
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/README.md +1 -1
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/pyproject.toml +1 -1
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/__init__.py +6 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/core/schemas/__init__.py +7 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/core/schemas/forecast.py +63 -0
- xrtm_data-0.5.0/src/xrtm/data/core/schemas/market.py +82 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/version.py +1 -1
- {xrtm_data-0.3.2 → xrtm_data-0.5.0/src/xrtm_data.egg-info}/PKG-INFO +2 -2
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm_data.egg-info/SOURCES.txt +2 -0
- xrtm_data-0.5.0/tests/test_market_snapshot.py +63 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/tests/test_schemas.py +63 -1
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/LICENSE +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/setup.cfg +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/core/__init__.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/core/interfaces.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/core/schemas/prior.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/core/schemas/trade.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/corpora/__init__.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/corpora/_builtin_corpora.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/corpora/real_binary.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/corpora/registry.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/providers/__init__.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/providers/online/__init__.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/providers/online/metaculus.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm/data/providers/online/polymarket.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm_data.egg-info/dependency_links.txt +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm_data.egg-info/requires.txt +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/src/xrtm_data.egg-info/top_level.txt +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/tests/test_corpus_registry.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/tests/test_prior_schemas.py +0 -0
- {xrtm_data-0.3.2 → xrtm_data-0.5.0}/tests/test_real_binary_corpus.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xrtm-data
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: The Snapshot Vault for XRTM.
|
|
5
5
|
Author-email: XRTM Team <moy@xrtm.org>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -17,7 +17,7 @@ Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
|
17
17
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
18
18
|
Dynamic: license-file
|
|
19
19
|
|
|
20
|
-
# xrtm-data v0.
|
|
20
|
+
# xrtm-data v0.5.0
|
|
21
21
|
|
|
22
22
|
[](https://pypi.org/project/xrtm-data/)
|
|
23
23
|
|
|
@@ -39,11 +39,14 @@ from xrtm.data.core.schemas import (
|
|
|
39
39
|
CausalNode,
|
|
40
40
|
ConfidenceInterval,
|
|
41
41
|
ForecastOutput,
|
|
42
|
+
ForecastProvenance,
|
|
42
43
|
ForecastQuestion,
|
|
43
44
|
ForecastRequest,
|
|
44
45
|
ForecastResult,
|
|
46
|
+
MarketSnapshot,
|
|
45
47
|
MetadataBase,
|
|
46
48
|
ReasoningTrace,
|
|
49
|
+
TokenUsage,
|
|
47
50
|
)
|
|
48
51
|
|
|
49
52
|
__all__ = [
|
|
@@ -63,4 +66,7 @@ __all__ = [
|
|
|
63
66
|
"CausalGraph",
|
|
64
67
|
"ReasoningTrace",
|
|
65
68
|
"ConfidenceInterval",
|
|
69
|
+
"TokenUsage",
|
|
70
|
+
"ForecastProvenance",
|
|
71
|
+
"MarketSnapshot",
|
|
66
72
|
]
|
|
@@ -25,12 +25,15 @@ from xrtm.data.core.schemas.forecast import (
|
|
|
25
25
|
CausalNode,
|
|
26
26
|
ConfidenceInterval,
|
|
27
27
|
ForecastOutput,
|
|
28
|
+
ForecastProvenance,
|
|
28
29
|
ForecastQuestion,
|
|
29
30
|
ForecastRequest,
|
|
30
31
|
ForecastResult,
|
|
31
32
|
MetadataBase,
|
|
32
33
|
ReasoningTrace,
|
|
34
|
+
TokenUsage,
|
|
33
35
|
)
|
|
36
|
+
from xrtm.data.core.schemas.market import MarketSnapshot
|
|
34
37
|
from xrtm.data.core.schemas.prior import BetaPrior, PriorState
|
|
35
38
|
from xrtm.data.core.schemas.trade import TradeEvent, TradeWindow
|
|
36
39
|
|
|
@@ -46,6 +49,10 @@ __all__ = [
|
|
|
46
49
|
"CausalGraph",
|
|
47
50
|
"ReasoningTrace",
|
|
48
51
|
"ConfidenceInterval",
|
|
52
|
+
"TokenUsage",
|
|
53
|
+
"ForecastProvenance",
|
|
54
|
+
# Market schemas
|
|
55
|
+
"MarketSnapshot",
|
|
49
56
|
# Prior schemas
|
|
50
57
|
"BetaPrior",
|
|
51
58
|
"PriorState",
|
|
@@ -178,6 +178,57 @@ class ConfidenceInterval(BaseModel):
|
|
|
178
178
|
level: float = Field(0.9, ge=0, le=1, description="Confidence level")
|
|
179
179
|
|
|
180
180
|
|
|
181
|
+
class TokenUsage(BaseModel):
|
|
182
|
+
r"""
|
|
183
|
+
Token accounting for a single forecast or inference run.
|
|
184
|
+
|
|
185
|
+
Attributes:
|
|
186
|
+
prompt_tokens: Total input tokens billed for this run.
|
|
187
|
+
completion_tokens: Total output tokens billed for this run.
|
|
188
|
+
cached_prompt_tokens: Input tokens served from a provider prefix cache.
|
|
189
|
+
reasoning_tokens: Output tokens spent on chain-of-thought reasoning.
|
|
190
|
+
total_tokens: Total billed tokens (prompt + completion).
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
prompt_tokens: int = Field(default=0, ge=0, description="Total input tokens billed for this run")
|
|
194
|
+
completion_tokens: int = Field(default=0, ge=0, description="Total output tokens billed for this run")
|
|
195
|
+
cached_prompt_tokens: int = Field(default=0, ge=0, description="Input tokens served from a provider prefix cache")
|
|
196
|
+
reasoning_tokens: int = Field(default=0, ge=0, description="Output tokens spent on chain-of-thought reasoning")
|
|
197
|
+
total_tokens: int = Field(default=0, ge=0, description="Total billed tokens (prompt + completion)")
|
|
198
|
+
|
|
199
|
+
@model_validator(mode="after")
|
|
200
|
+
def _fill_total_tokens(self) -> "TokenUsage":
|
|
201
|
+
r"""Derive ``total_tokens`` when a producer did not provide it."""
|
|
202
|
+
if self.total_tokens == 0:
|
|
203
|
+
self.total_tokens = self.prompt_tokens + self.completion_tokens
|
|
204
|
+
return self
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class ForecastProvenance(BaseModel):
|
|
208
|
+
r"""
|
|
209
|
+
Operational provenance for a forecast: which engine, model, and prompt produced it.
|
|
210
|
+
|
|
211
|
+
Attributes:
|
|
212
|
+
provider: Inference provider family (e.g. ``"deepseek"``, ``"typesafe"``).
|
|
213
|
+
model_id: Model identifier as requested (e.g. ``"deepseek-flash"``).
|
|
214
|
+
model_version: Model version or checkpoint reported by the provider.
|
|
215
|
+
prompt_id: Identifier or version of the prompt template used.
|
|
216
|
+
temperature: Sampling temperature used for the run.
|
|
217
|
+
thinking: Whether provider reasoning/thinking mode was enabled.
|
|
218
|
+
cache_hit: True when the result was served from a cache.
|
|
219
|
+
run_id: Identifier shared by all calls belonging to one forecast run.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
provider: Optional[str] = Field(default=None, description="Inference provider family (e.g. 'deepseek')")
|
|
223
|
+
model_id: Optional[str] = Field(default=None, description="Model identifier as requested")
|
|
224
|
+
model_version: Optional[str] = Field(default=None, description="Model version or checkpoint reported by the provider")
|
|
225
|
+
prompt_id: Optional[str] = Field(default=None, description="Identifier or version of the prompt template used")
|
|
226
|
+
temperature: Optional[float] = Field(default=None, description="Sampling temperature used for the run")
|
|
227
|
+
thinking: Optional[bool] = Field(default=None, description="Whether provider reasoning/thinking mode was enabled")
|
|
228
|
+
cache_hit: bool = Field(default=False, description="True when the result was served from a cache")
|
|
229
|
+
run_id: Optional[str] = Field(default=None, description="Identifier shared by all calls belonging to one forecast run")
|
|
230
|
+
|
|
231
|
+
|
|
181
232
|
class MappingCompatibleModel(BaseModel):
|
|
182
233
|
r"""Base model with lightweight dict-style compatibility helpers."""
|
|
183
234
|
|
|
@@ -293,6 +344,16 @@ class ForecastOutput(BaseModel):
|
|
|
293
344
|
description="Ordered workflow stages executed for this forecast result",
|
|
294
345
|
)
|
|
295
346
|
calibration_metrics: Dict[str, Any] = Field(default_factory=dict, description="Performance metrics")
|
|
347
|
+
parse_status: str = Field(
|
|
348
|
+
default="unknown",
|
|
349
|
+
description="Parse outcome for the model output: 'ok', 'empty_content', 'invalid_json', "
|
|
350
|
+
"'schema_error', 'provider_error', or 'unknown'.",
|
|
351
|
+
)
|
|
352
|
+
usage: TokenUsage = Field(default_factory=TokenUsage, description="Token accounting for this forecast")
|
|
353
|
+
provenance: Optional[ForecastProvenance] = Field(
|
|
354
|
+
default=None,
|
|
355
|
+
description="Model and prompt provenance for this forecast",
|
|
356
|
+
)
|
|
296
357
|
metadata: MetadataBase = Field(default_factory=MetadataBase) # type: ignore[arg-type]
|
|
297
358
|
|
|
298
359
|
@model_validator(mode="before")
|
|
@@ -458,6 +519,8 @@ __all__ = [
|
|
|
458
519
|
"ReasoningTrace",
|
|
459
520
|
"ForecastOutput",
|
|
460
521
|
"ForecastResult",
|
|
522
|
+
"TokenUsage",
|
|
523
|
+
"ForecastProvenance",
|
|
461
524
|
]
|
|
462
525
|
|
|
463
526
|
ForecastResult = ForecastOutput
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
# Copyright 2026 XRTM Team. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
r"""Point-in-time market snapshot schema.
|
|
17
|
+
|
|
18
|
+
A shared contract for prediction-market state (price, book, liquidity) that
|
|
19
|
+
applications, evaluators, and backtests can all consume without re-inventing
|
|
20
|
+
venue-specific shapes.
|
|
21
|
+
|
|
22
|
+
Zero Leakage: every snapshot carries the ``snapshot_time`` at which the world
|
|
23
|
+
state was frozen; no data after that instant should influence a forecast.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from datetime import datetime, timezone
|
|
27
|
+
from typing import Any, Dict, Optional
|
|
28
|
+
|
|
29
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
30
|
+
|
|
31
|
+
__all__ = ["MarketSnapshot"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MarketSnapshot(BaseModel):
|
|
35
|
+
r"""Point-in-time state of a prediction market.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
market_id: Venue-specific market identifier.
|
|
39
|
+
question_id: Optional link to the ``ForecastQuestion`` this market resolves.
|
|
40
|
+
probability: Implied/mid probability of the primary outcome (0-1).
|
|
41
|
+
best_bid: Best bid price (0-1).
|
|
42
|
+
best_ask: Best ask price (0-1).
|
|
43
|
+
spread: Bid/ask spread (0-1), derived from bid/ask when omitted.
|
|
44
|
+
volume_24h: Trailing 24h volume, in the venue's currency.
|
|
45
|
+
liquidity: Available liquidity, in the venue's currency.
|
|
46
|
+
snapshot_time: The "Time T" at which the market state was frozen.
|
|
47
|
+
source: Venue/source identifier (e.g. ``"polymarket"``).
|
|
48
|
+
raw_data: Original venue payload for debugging/audit.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
market_id: str = Field(..., description="Venue-specific market identifier")
|
|
52
|
+
question_id: Optional[str] = Field(default=None, description="Linked ForecastQuestion id")
|
|
53
|
+
probability: Optional[float] = Field(default=None, ge=0, le=1, description="Implied/mid probability")
|
|
54
|
+
best_bid: Optional[float] = Field(default=None, ge=0, le=1, description="Best bid price")
|
|
55
|
+
best_ask: Optional[float] = Field(default=None, ge=0, le=1, description="Best ask price")
|
|
56
|
+
spread: Optional[float] = Field(default=None, ge=0, le=1, description="Bid/ask spread")
|
|
57
|
+
volume_24h: Optional[float] = Field(default=None, ge=0, description="Trailing 24h volume")
|
|
58
|
+
liquidity: Optional[float] = Field(default=None, ge=0, description="Available liquidity")
|
|
59
|
+
snapshot_time: datetime = Field(
|
|
60
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
61
|
+
description="Zero Leakage: the instant this market state was frozen",
|
|
62
|
+
)
|
|
63
|
+
source: Optional[str] = Field(default=None, description="Venue/source identifier")
|
|
64
|
+
raw_data: Optional[Dict[str, Any]] = Field(default=None, description="Original venue payload")
|
|
65
|
+
|
|
66
|
+
@field_validator("snapshot_time", mode="after")
|
|
67
|
+
@classmethod
|
|
68
|
+
def _normalize_snapshot_time(cls, value: datetime) -> datetime:
|
|
69
|
+
r"""Store the snapshot boundary as a timezone-aware UTC datetime."""
|
|
70
|
+
if value.tzinfo is None:
|
|
71
|
+
return value.replace(tzinfo=timezone.utc)
|
|
72
|
+
return value.astimezone(timezone.utc)
|
|
73
|
+
|
|
74
|
+
@model_validator(mode="after")
|
|
75
|
+
def _validate_book(self) -> "MarketSnapshot":
|
|
76
|
+
r"""Derive the spread and reject crossed books."""
|
|
77
|
+
if self.best_bid is not None and self.best_ask is not None:
|
|
78
|
+
if self.best_bid > self.best_ask:
|
|
79
|
+
raise ValueError("best_bid must not exceed best_ask")
|
|
80
|
+
if self.spread is None:
|
|
81
|
+
self.spread = round(self.best_ask - self.best_bid, 10)
|
|
82
|
+
return self
|
|
@@ -21,7 +21,7 @@ This module provides the single source of truth for the package version.
|
|
|
21
21
|
|
|
22
22
|
__all__ = ["__version__", "__author__", "__contact__", "__license__", "__copyright__"]
|
|
23
23
|
|
|
24
|
-
__version__ = "0.
|
|
24
|
+
__version__ = "0.5.0"
|
|
25
25
|
__author__ = "XRTM Team"
|
|
26
26
|
__contact__ = "moy@xrtm.org"
|
|
27
27
|
__license__ = "Apache-2.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xrtm-data
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: The Snapshot Vault for XRTM.
|
|
5
5
|
Author-email: XRTM Team <moy@xrtm.org>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -17,7 +17,7 @@ Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
|
17
17
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
18
18
|
Dynamic: license-file
|
|
19
19
|
|
|
20
|
-
# xrtm-data v0.
|
|
20
|
+
# xrtm-data v0.5.0
|
|
21
21
|
|
|
22
22
|
[](https://pypi.org/project/xrtm-data/)
|
|
23
23
|
|
|
@@ -7,6 +7,7 @@ src/xrtm/data/core/__init__.py
|
|
|
7
7
|
src/xrtm/data/core/interfaces.py
|
|
8
8
|
src/xrtm/data/core/schemas/__init__.py
|
|
9
9
|
src/xrtm/data/core/schemas/forecast.py
|
|
10
|
+
src/xrtm/data/core/schemas/market.py
|
|
10
11
|
src/xrtm/data/core/schemas/prior.py
|
|
11
12
|
src/xrtm/data/core/schemas/trade.py
|
|
12
13
|
src/xrtm/data/corpora/__init__.py
|
|
@@ -23,6 +24,7 @@ src/xrtm_data.egg-info/dependency_links.txt
|
|
|
23
24
|
src/xrtm_data.egg-info/requires.txt
|
|
24
25
|
src/xrtm_data.egg-info/top_level.txt
|
|
25
26
|
tests/test_corpus_registry.py
|
|
27
|
+
tests/test_market_snapshot.py
|
|
26
28
|
tests/test_prior_schemas.py
|
|
27
29
|
tests/test_real_binary_corpus.py
|
|
28
30
|
tests/test_schemas.py
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
# Copyright 2026 XRTM Team. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
r"""Tests for the MarketSnapshot schema."""
|
|
17
|
+
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
|
|
20
|
+
import pytest
|
|
21
|
+
|
|
22
|
+
from xrtm.data import MarketSnapshot
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_snapshot_normalizes_naive_time_to_utc():
|
|
26
|
+
snapshot = MarketSnapshot(market_id="m1", snapshot_time=datetime(2026, 9, 26, 12, 0))
|
|
27
|
+
assert snapshot.snapshot_time.tzinfo == timezone.utc
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_spread_is_derived_from_book():
|
|
31
|
+
snapshot = MarketSnapshot(market_id="m2", best_bid=0.40, best_ask=0.44)
|
|
32
|
+
assert snapshot.spread == pytest.approx(0.04)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_explicit_spread_is_preserved():
|
|
36
|
+
snapshot = MarketSnapshot(market_id="m3", best_bid=0.40, best_ask=0.44, spread=0.05)
|
|
37
|
+
assert snapshot.spread == pytest.approx(0.05)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_crossed_book_is_rejected():
|
|
41
|
+
with pytest.raises(ValueError, match="best_bid"):
|
|
42
|
+
MarketSnapshot(market_id="m4", best_bid=0.60, best_ask=0.50)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_snapshot_serializes_and_round_trips():
|
|
46
|
+
snapshot = MarketSnapshot(
|
|
47
|
+
market_id="m5",
|
|
48
|
+
question_id="q5",
|
|
49
|
+
probability=0.42,
|
|
50
|
+
best_bid=0.41,
|
|
51
|
+
best_ask=0.43,
|
|
52
|
+
volume_24h=150_000,
|
|
53
|
+
liquidity=20_000,
|
|
54
|
+
source="polymarket",
|
|
55
|
+
raw_data={"token_id": "123"},
|
|
56
|
+
)
|
|
57
|
+
payload = snapshot.model_dump(mode="json")
|
|
58
|
+
assert payload["spread"] == pytest.approx(0.02)
|
|
59
|
+
|
|
60
|
+
reloaded = MarketSnapshot.model_validate(payload)
|
|
61
|
+
assert reloaded.market_id == "m5"
|
|
62
|
+
assert reloaded.question_id == "q5"
|
|
63
|
+
assert reloaded.raw_data == {"token_id": "123"}
|
|
@@ -16,7 +16,16 @@ from datetime import datetime, timedelta, timezone
|
|
|
16
16
|
|
|
17
17
|
import pytest
|
|
18
18
|
|
|
19
|
-
from xrtm.data import
|
|
19
|
+
from xrtm.data import (
|
|
20
|
+
CausalEdge,
|
|
21
|
+
CausalNode,
|
|
22
|
+
ForecastOutput,
|
|
23
|
+
ForecastProvenance,
|
|
24
|
+
ForecastQuestion,
|
|
25
|
+
ForecastResult,
|
|
26
|
+
MetadataBase,
|
|
27
|
+
TokenUsage,
|
|
28
|
+
)
|
|
20
29
|
from xrtm.data.core.schemas import TradeEvent, TradeWindow
|
|
21
30
|
|
|
22
31
|
|
|
@@ -202,3 +211,56 @@ def test_forecast_question_context_serializes():
|
|
|
202
211
|
|
|
203
212
|
reloaded = ForecastQuestion.model_validate(payload)
|
|
204
213
|
assert reloaded.context == {"key": "value", "nested": {"a": 1}}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_token_usage_derives_total():
|
|
217
|
+
"""total_tokens is derived when a producer omits it."""
|
|
218
|
+
usage = TokenUsage(prompt_tokens=1200, completion_tokens=300)
|
|
219
|
+
assert usage.total_tokens == 1500
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_token_usage_preserves_explicit_total():
|
|
223
|
+
"""An explicit total_tokens from the provider is not overwritten."""
|
|
224
|
+
usage = TokenUsage(prompt_tokens=10, completion_tokens=5, total_tokens=99)
|
|
225
|
+
assert usage.total_tokens == 99
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def test_forecast_output_telemetry_defaults():
|
|
229
|
+
"""Telemetry fields are optional and safe for legacy producers."""
|
|
230
|
+
output = ForecastOutput(question_id="q_telemetry", probability=0.5, reasoning="legacy")
|
|
231
|
+
assert output.parse_status == "unknown"
|
|
232
|
+
assert output.usage.total_tokens == 0
|
|
233
|
+
assert output.provenance is None
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_forecast_output_telemetry_round_trip():
|
|
237
|
+
"""Telemetry survives JSON serialization and re-validation."""
|
|
238
|
+
output = ForecastOutput(
|
|
239
|
+
forecast_request_id="q_telemetry_2",
|
|
240
|
+
probability=0.72,
|
|
241
|
+
reasoning="telemetry round trip",
|
|
242
|
+
parse_status="ok",
|
|
243
|
+
usage=TokenUsage(
|
|
244
|
+
prompt_tokens=900,
|
|
245
|
+
completion_tokens=250,
|
|
246
|
+
cached_prompt_tokens=800,
|
|
247
|
+
reasoning_tokens=120,
|
|
248
|
+
),
|
|
249
|
+
provenance=ForecastProvenance(
|
|
250
|
+
provider="deepseek",
|
|
251
|
+
model_id="deepseek-flash",
|
|
252
|
+
prompt_id="analyst-v2",
|
|
253
|
+
temperature=0.2,
|
|
254
|
+
thinking=False,
|
|
255
|
+
cache_hit=False,
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
payload = output.model_dump(mode="json")
|
|
259
|
+
assert payload["parse_status"] == "ok"
|
|
260
|
+
assert payload["usage"]["total_tokens"] == 1150
|
|
261
|
+
assert payload["provenance"]["model_id"] == "deepseek-flash"
|
|
262
|
+
|
|
263
|
+
reloaded = ForecastOutput.model_validate(payload)
|
|
264
|
+
assert reloaded.usage.cached_prompt_tokens == 800
|
|
265
|
+
assert reloaded.provenance is not None
|
|
266
|
+
assert reloaded.provenance.provider == "deepseek"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|