priorish-sdk 0.1.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.
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.eggs/
|
|
8
|
+
*.egg
|
|
9
|
+
.env
|
|
10
|
+
.env.local
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
neo4j_data/
|
|
14
|
+
neo4j_logs/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
*.ipynb_checkpoints/
|
|
19
|
+
.gstack/
|
|
20
|
+
.claude/
|
|
21
|
+
_index.sqlite
|
|
22
|
+
rust-preprocessor/target/
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Priorish Python SDK.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
from priorish_sdk import PriorishClient
|
|
5
|
+
|
|
6
|
+
client = PriorishClient(api_key="your-key")
|
|
7
|
+
results = client.search("nonfarm payrolls", as_of="2026-03-01T00:00:00Z")
|
|
8
|
+
diff = client.diff("nonfarm payrolls", t1="2026-03-01", t2="2026-04-01")
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from priorish_sdk.client import PriorishClient
|
|
12
|
+
from priorish_sdk.models import (
|
|
13
|
+
BaseRateResponse,
|
|
14
|
+
BatchSearchResponse,
|
|
15
|
+
BatchSearchResultSet,
|
|
16
|
+
DiffChange,
|
|
17
|
+
DiffResponse,
|
|
18
|
+
EdgeDetail,
|
|
19
|
+
EntityDiffResponse,
|
|
20
|
+
EpisodeDetail,
|
|
21
|
+
FactProvenance,
|
|
22
|
+
GraphNeighborsResponse,
|
|
23
|
+
NeighborEntity,
|
|
24
|
+
Provenance,
|
|
25
|
+
RelatedFact,
|
|
26
|
+
RevisionHistoryResponse,
|
|
27
|
+
SagaEpisode,
|
|
28
|
+
SagaGroup,
|
|
29
|
+
SagaListResponse,
|
|
30
|
+
SagaSummary,
|
|
31
|
+
SagaTimelineResponse,
|
|
32
|
+
SearchResponse,
|
|
33
|
+
SearchResult,
|
|
34
|
+
SourceStatus,
|
|
35
|
+
StatusResponse,
|
|
36
|
+
TimelineEvent,
|
|
37
|
+
TimelineResponse,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"PriorishClient",
|
|
42
|
+
"BaseRateResponse",
|
|
43
|
+
"BatchSearchResponse",
|
|
44
|
+
"BatchSearchResultSet",
|
|
45
|
+
"DiffChange",
|
|
46
|
+
"DiffResponse",
|
|
47
|
+
"EdgeDetail",
|
|
48
|
+
"EntityDiffResponse",
|
|
49
|
+
"EpisodeDetail",
|
|
50
|
+
"FactProvenance",
|
|
51
|
+
"GraphNeighborsResponse",
|
|
52
|
+
"NeighborEntity",
|
|
53
|
+
"Provenance",
|
|
54
|
+
"RelatedFact",
|
|
55
|
+
"RevisionHistoryResponse",
|
|
56
|
+
"SagaEpisode",
|
|
57
|
+
"SagaGroup",
|
|
58
|
+
"SagaListResponse",
|
|
59
|
+
"SagaSummary",
|
|
60
|
+
"SagaTimelineResponse",
|
|
61
|
+
"SearchResponse",
|
|
62
|
+
"SearchResult",
|
|
63
|
+
"SourceStatus",
|
|
64
|
+
"StatusResponse",
|
|
65
|
+
"TimelineEvent",
|
|
66
|
+
"TimelineResponse",
|
|
67
|
+
]
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""Priorish Python SDK client."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from priorish_sdk.models import (
|
|
8
|
+
BaseRateResponse,
|
|
9
|
+
BatchSearchResponse,
|
|
10
|
+
CoverageResponse,
|
|
11
|
+
DiffResponse,
|
|
12
|
+
EntityDiffResponse,
|
|
13
|
+
FactProvenance,
|
|
14
|
+
GraphNeighborsResponse,
|
|
15
|
+
RevisionHistoryResponse,
|
|
16
|
+
SagaListResponse,
|
|
17
|
+
SagaTimelineResponse,
|
|
18
|
+
SearchResponse,
|
|
19
|
+
SeriesResponse,
|
|
20
|
+
StatusResponse,
|
|
21
|
+
TimelineResponse,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PriorishClient:
|
|
26
|
+
"""Client for the Priorish API.
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
client = PriorishClient(api_key="your-key")
|
|
30
|
+
results = client.search("nonfarm payrolls")
|
|
31
|
+
diff = client.diff("nonfarm payrolls", t1="2026-03-01", t2="2026-04-01")
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
api_key: str,
|
|
37
|
+
base_url: str = "https://api.priori.sh",
|
|
38
|
+
timeout: float = 30.0,
|
|
39
|
+
):
|
|
40
|
+
self._client = httpx.Client(
|
|
41
|
+
base_url=base_url,
|
|
42
|
+
headers={"X-API-Key": api_key},
|
|
43
|
+
timeout=timeout,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def search(
|
|
47
|
+
self,
|
|
48
|
+
query: str,
|
|
49
|
+
as_of: str | datetime | None = None,
|
|
50
|
+
start_date: str | datetime | None = None,
|
|
51
|
+
end_date: str | datetime | None = None,
|
|
52
|
+
sources: list[str] | None = None,
|
|
53
|
+
limit: int = 10,
|
|
54
|
+
temperature: float = 0.0,
|
|
55
|
+
natural_language: bool = False,
|
|
56
|
+
) -> SearchResponse:
|
|
57
|
+
"""Temporal search: what was knowable at a specific point in time."""
|
|
58
|
+
params: dict = {
|
|
59
|
+
"query": query,
|
|
60
|
+
"limit": limit,
|
|
61
|
+
}
|
|
62
|
+
if natural_language:
|
|
63
|
+
params["natural_language"] = "true"
|
|
64
|
+
if temperature > 0.0:
|
|
65
|
+
params["temperature"] = temperature
|
|
66
|
+
if as_of:
|
|
67
|
+
params["as_of"] = str(as_of) if isinstance(as_of, datetime) else as_of
|
|
68
|
+
if start_date:
|
|
69
|
+
params["start_date"] = (
|
|
70
|
+
str(start_date) if isinstance(start_date, datetime) else start_date
|
|
71
|
+
)
|
|
72
|
+
if end_date:
|
|
73
|
+
params["end_date"] = str(end_date) if isinstance(end_date, datetime) else end_date
|
|
74
|
+
if sources:
|
|
75
|
+
params["sources"] = ",".join(sources)
|
|
76
|
+
|
|
77
|
+
resp = self._client.get("/search", params=params)
|
|
78
|
+
resp.raise_for_status()
|
|
79
|
+
return SearchResponse(**resp.json())
|
|
80
|
+
|
|
81
|
+
def diff(
|
|
82
|
+
self,
|
|
83
|
+
query: str,
|
|
84
|
+
t1: str | datetime,
|
|
85
|
+
t2: str | datetime,
|
|
86
|
+
sources: list[str] | None = None,
|
|
87
|
+
) -> DiffResponse:
|
|
88
|
+
"""Knowledge diff: what changed between two timestamps."""
|
|
89
|
+
params = {
|
|
90
|
+
"query": query,
|
|
91
|
+
"t1": str(t1) if isinstance(t1, datetime) else t1,
|
|
92
|
+
"t2": str(t2) if isinstance(t2, datetime) else t2,
|
|
93
|
+
}
|
|
94
|
+
if sources:
|
|
95
|
+
params["sources"] = ",".join(sources)
|
|
96
|
+
|
|
97
|
+
resp = self._client.get("/diff", params=params)
|
|
98
|
+
resp.raise_for_status()
|
|
99
|
+
return DiffResponse(**resp.json())
|
|
100
|
+
|
|
101
|
+
def status(self) -> StatusResponse:
|
|
102
|
+
"""Get source freshness and system status."""
|
|
103
|
+
resp = self._client.get("/status")
|
|
104
|
+
resp.raise_for_status()
|
|
105
|
+
return StatusResponse(**resp.json())
|
|
106
|
+
|
|
107
|
+
def sagas(self, source: str | None = None) -> SagaListResponse:
|
|
108
|
+
"""List all sagas with metadata."""
|
|
109
|
+
params = {}
|
|
110
|
+
if source:
|
|
111
|
+
params["source"] = source
|
|
112
|
+
resp = self._client.get("/sagas", params=params)
|
|
113
|
+
resp.raise_for_status()
|
|
114
|
+
return SagaListResponse(**resp.json())
|
|
115
|
+
|
|
116
|
+
def saga_timeline(
|
|
117
|
+
self,
|
|
118
|
+
name: str,
|
|
119
|
+
limit: int = 50,
|
|
120
|
+
offset: int = 0,
|
|
121
|
+
) -> SagaTimelineResponse:
|
|
122
|
+
"""Get a saga's timeline: episodes in chronological order."""
|
|
123
|
+
resp = self._client.get(
|
|
124
|
+
f"/sagas/{name}",
|
|
125
|
+
params={"limit": limit, "offset": offset},
|
|
126
|
+
)
|
|
127
|
+
resp.raise_for_status()
|
|
128
|
+
return SagaTimelineResponse(**resp.json())
|
|
129
|
+
|
|
130
|
+
def saga_diff(
|
|
131
|
+
self,
|
|
132
|
+
name: str,
|
|
133
|
+
episode_1: str | None = None,
|
|
134
|
+
episode_2: str | None = None,
|
|
135
|
+
) -> DiffResponse:
|
|
136
|
+
"""Diff two episodes within a saga. Defaults to two most recent."""
|
|
137
|
+
params = {}
|
|
138
|
+
if episode_1:
|
|
139
|
+
params["episode_1"] = episode_1
|
|
140
|
+
if episode_2:
|
|
141
|
+
params["episode_2"] = episode_2
|
|
142
|
+
resp = self._client.get(f"/sagas/{name}/diff", params=params)
|
|
143
|
+
resp.raise_for_status()
|
|
144
|
+
return DiffResponse(**resp.json())
|
|
145
|
+
|
|
146
|
+
def search_batch(
|
|
147
|
+
self,
|
|
148
|
+
query: str,
|
|
149
|
+
timestamps: list[str | datetime],
|
|
150
|
+
sources: list[str] | None = None,
|
|
151
|
+
limit: int = 10,
|
|
152
|
+
) -> BatchSearchResponse:
|
|
153
|
+
"""Run the same query at multiple timestamps for backtesting."""
|
|
154
|
+
ts_strs = [str(t) if isinstance(t, datetime) else t for t in timestamps]
|
|
155
|
+
params: dict = {
|
|
156
|
+
"query": query,
|
|
157
|
+
"timestamps": ",".join(ts_strs),
|
|
158
|
+
"limit": limit,
|
|
159
|
+
}
|
|
160
|
+
if sources:
|
|
161
|
+
params["sources"] = ",".join(sources)
|
|
162
|
+
resp = self._client.get("/search/batch", params=params)
|
|
163
|
+
resp.raise_for_status()
|
|
164
|
+
return BatchSearchResponse(**resp.json())
|
|
165
|
+
|
|
166
|
+
def timeline(
|
|
167
|
+
self,
|
|
168
|
+
entity: str | None = None,
|
|
169
|
+
saga: str | None = None,
|
|
170
|
+
as_of: str | datetime | None = None,
|
|
171
|
+
sources: list[str] | None = None,
|
|
172
|
+
limit: int = 50,
|
|
173
|
+
offset: int = 0,
|
|
174
|
+
) -> TimelineResponse:
|
|
175
|
+
"""Temporal evolution: how an entity's facts changed over time."""
|
|
176
|
+
params: dict = {"limit": limit, "offset": offset}
|
|
177
|
+
if entity:
|
|
178
|
+
params["entity"] = entity
|
|
179
|
+
if saga:
|
|
180
|
+
params["saga"] = saga
|
|
181
|
+
if as_of:
|
|
182
|
+
params["as_of"] = str(as_of) if isinstance(as_of, datetime) else as_of
|
|
183
|
+
if sources:
|
|
184
|
+
params["sources"] = ",".join(sources)
|
|
185
|
+
resp = self._client.get("/timeline", params=params)
|
|
186
|
+
resp.raise_for_status()
|
|
187
|
+
return TimelineResponse(**resp.json())
|
|
188
|
+
|
|
189
|
+
def provenance(self, fact_uuid: str) -> FactProvenance:
|
|
190
|
+
"""Full provenance for a fact: source episodes, raw content, related facts."""
|
|
191
|
+
resp = self._client.get(f"/provenance/{fact_uuid}")
|
|
192
|
+
resp.raise_for_status()
|
|
193
|
+
return FactProvenance(**resp.json())
|
|
194
|
+
|
|
195
|
+
def entity_diff(
|
|
196
|
+
self,
|
|
197
|
+
entity_name: str,
|
|
198
|
+
t1: str | datetime,
|
|
199
|
+
t2: str | datetime,
|
|
200
|
+
) -> EntityDiffResponse:
|
|
201
|
+
"""Entity-centric diff: what changed for an entity between t1 and t2."""
|
|
202
|
+
params = {
|
|
203
|
+
"t1": str(t1) if isinstance(t1, datetime) else t1,
|
|
204
|
+
"t2": str(t2) if isinstance(t2, datetime) else t2,
|
|
205
|
+
}
|
|
206
|
+
resp = self._client.get(f"/entities/{entity_name}/diff", params=params)
|
|
207
|
+
resp.raise_for_status()
|
|
208
|
+
return EntityDiffResponse(**resp.json())
|
|
209
|
+
|
|
210
|
+
def entity_revisions(
|
|
211
|
+
self,
|
|
212
|
+
entity_name: str,
|
|
213
|
+
as_of: str | datetime | None = None,
|
|
214
|
+
) -> RevisionHistoryResponse:
|
|
215
|
+
"""Revision/contradiction history for an entity."""
|
|
216
|
+
params: dict = {}
|
|
217
|
+
if as_of:
|
|
218
|
+
params["as_of"] = str(as_of) if isinstance(as_of, datetime) else as_of
|
|
219
|
+
resp = self._client.get(f"/entities/{entity_name}/revisions", params=params)
|
|
220
|
+
resp.raise_for_status()
|
|
221
|
+
return RevisionHistoryResponse(**resp.json())
|
|
222
|
+
|
|
223
|
+
def entity_neighbors(
|
|
224
|
+
self,
|
|
225
|
+
entity_name: str,
|
|
226
|
+
relation_types: list[str] | None = None,
|
|
227
|
+
as_of: str | datetime | None = None,
|
|
228
|
+
limit: int = 100,
|
|
229
|
+
) -> GraphNeighborsResponse:
|
|
230
|
+
"""Graph neighbors: entities connected via edges."""
|
|
231
|
+
params: dict = {"limit": limit}
|
|
232
|
+
if relation_types:
|
|
233
|
+
params["relation_types"] = ",".join(relation_types)
|
|
234
|
+
if as_of:
|
|
235
|
+
params["as_of"] = str(as_of) if isinstance(as_of, datetime) else as_of
|
|
236
|
+
resp = self._client.get(f"/entities/{entity_name}/neighbors", params=params)
|
|
237
|
+
resp.raise_for_status()
|
|
238
|
+
return GraphNeighborsResponse(**resp.json())
|
|
239
|
+
|
|
240
|
+
def base_rates(
|
|
241
|
+
self,
|
|
242
|
+
relation_type: str,
|
|
243
|
+
entity_type: str | None = None,
|
|
244
|
+
) -> BaseRateResponse:
|
|
245
|
+
"""Base rate query: all edges of a given type for quantitative analysis."""
|
|
246
|
+
params: dict = {}
|
|
247
|
+
if entity_type:
|
|
248
|
+
params["entity_type"] = entity_type
|
|
249
|
+
resp = self._client.get(f"/base-rates/{relation_type}", params=params)
|
|
250
|
+
resp.raise_for_status()
|
|
251
|
+
return BaseRateResponse(**resp.json())
|
|
252
|
+
|
|
253
|
+
def relation_types(
|
|
254
|
+
self,
|
|
255
|
+
entity_type: str | None = None,
|
|
256
|
+
) -> list[dict]:
|
|
257
|
+
"""Discover available relation types and their counts."""
|
|
258
|
+
params: dict = {}
|
|
259
|
+
if entity_type:
|
|
260
|
+
params["entity_type"] = entity_type
|
|
261
|
+
resp = self._client.get("/relation-types", params=params)
|
|
262
|
+
resp.raise_for_status()
|
|
263
|
+
return resp.json()
|
|
264
|
+
|
|
265
|
+
def series(
|
|
266
|
+
self,
|
|
267
|
+
entity: str,
|
|
268
|
+
relation_type: str,
|
|
269
|
+
format: str = "json",
|
|
270
|
+
start_date: str | datetime | None = None,
|
|
271
|
+
end_date: str | datetime | None = None,
|
|
272
|
+
) -> SeriesResponse:
|
|
273
|
+
"""Time series data for an entity + relation type."""
|
|
274
|
+
params: dict = {"format": format}
|
|
275
|
+
if start_date:
|
|
276
|
+
params["start_date"] = (
|
|
277
|
+
str(start_date) if isinstance(start_date, datetime) else start_date
|
|
278
|
+
)
|
|
279
|
+
if end_date:
|
|
280
|
+
params["end_date"] = str(end_date) if isinstance(end_date, datetime) else end_date
|
|
281
|
+
resp = self._client.get(f"/series/{entity}/{relation_type}", params=params)
|
|
282
|
+
resp.raise_for_status()
|
|
283
|
+
return SeriesResponse(**resp.json())
|
|
284
|
+
|
|
285
|
+
def coverage(self) -> CoverageResponse:
|
|
286
|
+
"""Coverage per source, entity type, and relation type."""
|
|
287
|
+
resp = self._client.get("/coverage")
|
|
288
|
+
resp.raise_for_status()
|
|
289
|
+
return CoverageResponse(**resp.json())
|
|
290
|
+
|
|
291
|
+
def provenance_chain(self, fact_uuid: str) -> dict:
|
|
292
|
+
"""Extended provenance with content hashes."""
|
|
293
|
+
resp = self._client.get(f"/provenance/{fact_uuid}/chain")
|
|
294
|
+
resp.raise_for_status()
|
|
295
|
+
return resp.json()
|
|
296
|
+
|
|
297
|
+
def provenance_pack(self, fact_uuid: str) -> dict:
|
|
298
|
+
"""Evidence bundle from R2 artifacts."""
|
|
299
|
+
resp = self._client.get(f"/provenance/{fact_uuid}/pack")
|
|
300
|
+
resp.raise_for_status()
|
|
301
|
+
return resp.json()
|
|
302
|
+
|
|
303
|
+
def close(self):
|
|
304
|
+
self._client.close()
|
|
305
|
+
|
|
306
|
+
def __enter__(self):
|
|
307
|
+
return self
|
|
308
|
+
|
|
309
|
+
def __exit__(self, *args):
|
|
310
|
+
self.close()
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""Priorish SDK response models."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Provenance(BaseModel):
|
|
9
|
+
origin_url: str
|
|
10
|
+
source_type: str
|
|
11
|
+
indexed_at: datetime
|
|
12
|
+
content_hash: str
|
|
13
|
+
valid_from: datetime
|
|
14
|
+
valid_to: datetime | None = None
|
|
15
|
+
superseded_by: str | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Evidence(BaseModel):
|
|
19
|
+
"""Structured evidentiary weight for LLM consumption."""
|
|
20
|
+
|
|
21
|
+
basis: str = Field(
|
|
22
|
+
description="'primary' (from source doc) or 'derived' (computed from other facts)"
|
|
23
|
+
)
|
|
24
|
+
status: str = Field(description="'current' or 'superseded'")
|
|
25
|
+
corroboration: int = Field(description="Number of independent source episodes")
|
|
26
|
+
quantitative: bool = Field(description="Contains numeric/dollar/percent values")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SearchResult(BaseModel):
|
|
30
|
+
id: str
|
|
31
|
+
content: str
|
|
32
|
+
score: float
|
|
33
|
+
confidence_score: float = Field(
|
|
34
|
+
default=0.0,
|
|
35
|
+
description="Deprecated. Use evidence object instead.",
|
|
36
|
+
)
|
|
37
|
+
evidence: Evidence | None = None
|
|
38
|
+
provenance: Provenance
|
|
39
|
+
fact_type: str = Field(default="extracted", description="'extracted' or 'derived'")
|
|
40
|
+
saga_name: str | None = None
|
|
41
|
+
saga_label: str | None = None
|
|
42
|
+
cluster_size: int = Field(
|
|
43
|
+
default=1, description="Number of results folded into this one by temperature."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SagaGroup(BaseModel):
|
|
48
|
+
saga_name: str
|
|
49
|
+
saga_label: str
|
|
50
|
+
source_type: str
|
|
51
|
+
results: list[SearchResult]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SearchResponse(BaseModel):
|
|
55
|
+
query: str
|
|
56
|
+
as_of: datetime
|
|
57
|
+
results: list[SearchResult]
|
|
58
|
+
total_count: int = Field(description="Total results available before pagination.")
|
|
59
|
+
offset: int = 0
|
|
60
|
+
temperature: float = 0.0
|
|
61
|
+
source_freshness: dict[str, datetime | None] = Field(default_factory=dict)
|
|
62
|
+
saga_groups: list[SagaGroup] = []
|
|
63
|
+
ungrouped: list[SearchResult] = []
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DiffChange(BaseModel):
|
|
67
|
+
type: str # "revised", "added", "retracted"
|
|
68
|
+
fact_id: str | None = None
|
|
69
|
+
source_type: str | None = None
|
|
70
|
+
document_at_t1: str | None = None
|
|
71
|
+
document_at_t2: str | None = None
|
|
72
|
+
summary: str
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class DiffResponse(BaseModel):
|
|
76
|
+
query: str
|
|
77
|
+
t1: datetime
|
|
78
|
+
t2: datetime
|
|
79
|
+
results_at_t1: list[SearchResult]
|
|
80
|
+
results_at_t2: list[SearchResult]
|
|
81
|
+
changes: list[DiffChange]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class EpisodeDetail(BaseModel):
|
|
85
|
+
"""An ingested source document."""
|
|
86
|
+
|
|
87
|
+
uuid: str
|
|
88
|
+
name: str
|
|
89
|
+
content: str
|
|
90
|
+
source_type: str
|
|
91
|
+
origin_url: str
|
|
92
|
+
content_hash: str
|
|
93
|
+
ingested_at: datetime
|
|
94
|
+
valid_at: datetime
|
|
95
|
+
chunks: list[dict] | None = None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class SagaSummary(BaseModel):
|
|
99
|
+
name: str
|
|
100
|
+
source_type: str
|
|
101
|
+
label: str
|
|
102
|
+
episode_count: int
|
|
103
|
+
earliest: datetime | None = None
|
|
104
|
+
latest: datetime | None = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class SagaListResponse(BaseModel):
|
|
108
|
+
sagas: list[SagaSummary]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class SagaEpisode(BaseModel):
|
|
112
|
+
uuid: str
|
|
113
|
+
name: str
|
|
114
|
+
valid_at: datetime
|
|
115
|
+
created_at: datetime
|
|
116
|
+
source_type: str
|
|
117
|
+
origin_url: str
|
|
118
|
+
content_preview: str
|
|
119
|
+
fact_count: int
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class SagaTimelineResponse(BaseModel):
|
|
123
|
+
saga_name: str
|
|
124
|
+
as_of: datetime
|
|
125
|
+
source_type: str
|
|
126
|
+
label: str
|
|
127
|
+
episodes: list[SagaEpisode]
|
|
128
|
+
total_count: int
|
|
129
|
+
offset: int = 0
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class RelatedFact(BaseModel):
|
|
133
|
+
"""A related fact with the same structure as the main fact."""
|
|
134
|
+
|
|
135
|
+
fact_uuid: str
|
|
136
|
+
fact: str
|
|
137
|
+
source_node: str
|
|
138
|
+
target_node: str
|
|
139
|
+
valid_at: datetime | None
|
|
140
|
+
invalid_at: datetime | None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class FactProvenance(BaseModel):
|
|
144
|
+
"""Full provenance for a single fact (edge)."""
|
|
145
|
+
|
|
146
|
+
fact_uuid: str
|
|
147
|
+
fact: str
|
|
148
|
+
source_node: str
|
|
149
|
+
target_node: str
|
|
150
|
+
valid_at: datetime | None
|
|
151
|
+
invalid_at: datetime | None
|
|
152
|
+
created_at: datetime
|
|
153
|
+
fact_type: str = "extracted"
|
|
154
|
+
source_chunk_index: int | None = None
|
|
155
|
+
episodes: list[EpisodeDetail]
|
|
156
|
+
related_facts: list[RelatedFact]
|
|
157
|
+
saga_name: str | None = None
|
|
158
|
+
saga_label: str | None = None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class BatchSearchResultSet(BaseModel):
|
|
162
|
+
as_of: datetime
|
|
163
|
+
results: list[SearchResult]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class BatchSearchResponse(BaseModel):
|
|
167
|
+
query: str
|
|
168
|
+
result_sets: list[BatchSearchResultSet]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class TimelineEvent(BaseModel):
|
|
172
|
+
"""A single temporal event in an entity's history."""
|
|
173
|
+
|
|
174
|
+
fact_uuid: str
|
|
175
|
+
fact: str
|
|
176
|
+
source_node: str
|
|
177
|
+
target_node: str
|
|
178
|
+
valid_at: datetime | None
|
|
179
|
+
invalid_at: datetime | None
|
|
180
|
+
created_at: datetime
|
|
181
|
+
source_type: str
|
|
182
|
+
origin_url: str
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class TimelineResponse(BaseModel):
|
|
186
|
+
entity: str
|
|
187
|
+
as_of: datetime
|
|
188
|
+
events: list[TimelineEvent]
|
|
189
|
+
total_count: int = Field(description="Total events available before pagination.")
|
|
190
|
+
offset: int = 0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class SourceStatus(BaseModel):
|
|
194
|
+
source: str
|
|
195
|
+
last_successful_ingest: datetime | None = None
|
|
196
|
+
status: str # "green", "yellow", "red"
|
|
197
|
+
document_count: int = 0
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class StatusResponse(BaseModel):
|
|
201
|
+
sources: list[SourceStatus]
|
|
202
|
+
total_documents: int = 0
|
|
203
|
+
total_entities: int = 0
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class EdgeDetail(BaseModel):
|
|
207
|
+
"""An extracted fact/edge with full metadata."""
|
|
208
|
+
|
|
209
|
+
id: str
|
|
210
|
+
fact: str
|
|
211
|
+
relation_type: str
|
|
212
|
+
source_entity: str
|
|
213
|
+
target_entity: str
|
|
214
|
+
valid_at: datetime | None
|
|
215
|
+
invalid_at: datetime | None
|
|
216
|
+
created_at: datetime
|
|
217
|
+
expired_at: datetime | None = None
|
|
218
|
+
superseded_by: str | None = None
|
|
219
|
+
structured_data: dict | None = None
|
|
220
|
+
origin_url: str = ""
|
|
221
|
+
source_type: str = ""
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class EntityDiffResponse(BaseModel):
|
|
225
|
+
entity: str
|
|
226
|
+
t1: datetime
|
|
227
|
+
t2: datetime
|
|
228
|
+
appeared: list[EdgeDetail]
|
|
229
|
+
invalidated: list[EdgeDetail]
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class RevisionHistoryResponse(BaseModel):
|
|
233
|
+
entity: str
|
|
234
|
+
revisions: list[EdgeDetail]
|
|
235
|
+
total_count: int
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class NeighborEntity(BaseModel):
|
|
239
|
+
entity_id: str
|
|
240
|
+
name: str
|
|
241
|
+
entity_type: str
|
|
242
|
+
relation_type: str
|
|
243
|
+
fact: str
|
|
244
|
+
valid_at: datetime | None
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class GraphNeighborsResponse(BaseModel):
|
|
248
|
+
entity: str
|
|
249
|
+
neighbors: list[NeighborEntity]
|
|
250
|
+
total_count: int
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class BaseRateResponse(BaseModel):
|
|
254
|
+
relation_type: str
|
|
255
|
+
entity_type: str | None
|
|
256
|
+
total_count: int
|
|
257
|
+
edges: list[EdgeDetail]
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class SeriesPoint(BaseModel):
|
|
261
|
+
valid_at: str | None = None
|
|
262
|
+
fact: str = ""
|
|
263
|
+
value: float | str | None = None
|
|
264
|
+
unit: str | None = None
|
|
265
|
+
period: str | None = None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class SeriesResponse(BaseModel):
|
|
269
|
+
entity: str
|
|
270
|
+
relation_type: str
|
|
271
|
+
count: int
|
|
272
|
+
data: list[dict]
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class CoverageSource(BaseModel):
|
|
276
|
+
name: str
|
|
277
|
+
doc_count: int = 0
|
|
278
|
+
entity_count: int = 0
|
|
279
|
+
edge_count: int = 0
|
|
280
|
+
earliest: str | None = None
|
|
281
|
+
latest: str | None = None
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class CoverageResponse(BaseModel):
|
|
285
|
+
sources: list[CoverageSource] = []
|
|
286
|
+
entity_types: list[dict] = []
|
|
287
|
+
relation_types: list[dict] = []
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "priorish-sdk"
|
|
3
|
+
description = "Python SDK for the Priorish temporal search API"
|
|
4
|
+
version = "0.1.0"
|
|
5
|
+
requires-python = ">=3.11,<4"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"httpx>=0.28.0",
|
|
8
|
+
"pydantic>=2.11.0",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[build-system]
|
|
12
|
+
requires = ["hatchling"]
|
|
13
|
+
build-backend = "hatchling.build"
|
|
14
|
+
|
|
15
|
+
[tool.hatch.build.targets.wheel]
|
|
16
|
+
packages = ["priorish_sdk"]
|
|
17
|
+
|
|
18
|
+
[tool.ruff]
|
|
19
|
+
line-length = 100
|
|
20
|
+
|
|
21
|
+
[tool.ruff.lint]
|
|
22
|
+
select = ["E", "F", "UP", "B", "SIM", "I"]
|
|
23
|
+
ignore = ["E501", "B008"]
|
|
24
|
+
|
|
25
|
+
[tool.ruff.format]
|
|
26
|
+
quote-style = "double"
|
|
27
|
+
indent-style = "space"
|