reportify-sdk 0.1.0__py3-none-any.whl
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.
- reportify_sdk/__init__.py +31 -0
- reportify_sdk/client.py +362 -0
- reportify_sdk/docs.py +173 -0
- reportify_sdk/exceptions.py +41 -0
- reportify_sdk/kb.py +76 -0
- reportify_sdk/stock.py +430 -0
- reportify_sdk/timeline.py +127 -0
- reportify_sdk-0.1.0.dist-info/METADATA +166 -0
- reportify_sdk-0.1.0.dist-info/RECORD +12 -0
- reportify_sdk-0.1.0.dist-info/WHEEL +5 -0
- reportify_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- reportify_sdk-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reportify SDK - Python client for Reportify API
|
|
3
|
+
|
|
4
|
+
A user-friendly SDK for accessing financial data, document search,
|
|
5
|
+
and knowledge base through the Reportify API.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from reportify_sdk import Reportify
|
|
9
|
+
|
|
10
|
+
client = Reportify(api_key="your-api-key")
|
|
11
|
+
docs = client.search("Tesla earnings", num=10)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from reportify_sdk.client import Reportify
|
|
15
|
+
from reportify_sdk.exceptions import (
|
|
16
|
+
ReportifyError,
|
|
17
|
+
AuthenticationError,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
NotFoundError,
|
|
20
|
+
APIError,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
__all__ = [
|
|
25
|
+
"Reportify",
|
|
26
|
+
"ReportifyError",
|
|
27
|
+
"AuthenticationError",
|
|
28
|
+
"RateLimitError",
|
|
29
|
+
"NotFoundError",
|
|
30
|
+
"APIError",
|
|
31
|
+
]
|
reportify_sdk/client.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reportify Client
|
|
3
|
+
|
|
4
|
+
Main client class for interacting with the Reportify API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from reportify_sdk.exceptions import (
|
|
12
|
+
APIError,
|
|
13
|
+
AuthenticationError,
|
|
14
|
+
NotFoundError,
|
|
15
|
+
RateLimitError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Reportify:
|
|
20
|
+
"""
|
|
21
|
+
Reportify API Client
|
|
22
|
+
|
|
23
|
+
A user-friendly client for accessing financial data, document search,
|
|
24
|
+
and knowledge base through the Reportify API.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
api_key: Your Reportify API key
|
|
28
|
+
base_url: Base URL for the API (default: https://api.reportify.cn)
|
|
29
|
+
timeout: Request timeout in seconds (default: 30)
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
>>> from reportify_sdk import Reportify
|
|
33
|
+
>>> client = Reportify(api_key="your-api-key")
|
|
34
|
+
>>> docs = client.search("Tesla earnings", num=10)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
DEFAULT_BASE_URL = "https://api.reportify.cn"
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
api_key: str,
|
|
42
|
+
base_url: str | None = None,
|
|
43
|
+
timeout: float = 30.0,
|
|
44
|
+
):
|
|
45
|
+
if not api_key:
|
|
46
|
+
raise AuthenticationError("API key is required")
|
|
47
|
+
|
|
48
|
+
self.api_key = api_key
|
|
49
|
+
self.base_url = (base_url or self.DEFAULT_BASE_URL).rstrip("/")
|
|
50
|
+
self.timeout = timeout
|
|
51
|
+
|
|
52
|
+
self._client = httpx.Client(
|
|
53
|
+
base_url=self.base_url,
|
|
54
|
+
headers=self._get_headers(),
|
|
55
|
+
timeout=timeout,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Initialize sub-modules (lazy loading)
|
|
59
|
+
self._stock = None
|
|
60
|
+
self._timeline = None
|
|
61
|
+
self._kb = None
|
|
62
|
+
self._docs = None
|
|
63
|
+
self._tools = None
|
|
64
|
+
|
|
65
|
+
def _get_headers(self) -> dict[str, str]:
|
|
66
|
+
"""Get default headers for API requests"""
|
|
67
|
+
return {
|
|
68
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
69
|
+
"Content-Type": "application/json",
|
|
70
|
+
"User-Agent": "reportify-sdk-python/0.1.0",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def _request(
|
|
74
|
+
self,
|
|
75
|
+
method: str,
|
|
76
|
+
path: str,
|
|
77
|
+
params: dict[str, Any] | None = None,
|
|
78
|
+
json: dict[str, Any] | None = None,
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
"""
|
|
81
|
+
Make an HTTP request to the API
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
method: HTTP method (GET, POST, etc.)
|
|
85
|
+
path: API endpoint path
|
|
86
|
+
params: Query parameters
|
|
87
|
+
json: JSON body data
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Response data as dictionary
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
AuthenticationError: If API key is invalid
|
|
94
|
+
RateLimitError: If rate limit is exceeded
|
|
95
|
+
NotFoundError: If resource is not found
|
|
96
|
+
APIError: For other API errors
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
response = self._client.request(
|
|
100
|
+
method=method,
|
|
101
|
+
url=path,
|
|
102
|
+
params=params,
|
|
103
|
+
json=json,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Handle error responses
|
|
107
|
+
if response.status_code == 401:
|
|
108
|
+
raise AuthenticationError()
|
|
109
|
+
elif response.status_code == 429:
|
|
110
|
+
raise RateLimitError()
|
|
111
|
+
elif response.status_code == 404:
|
|
112
|
+
raise NotFoundError()
|
|
113
|
+
elif response.status_code >= 400:
|
|
114
|
+
error_msg = response.text
|
|
115
|
+
try:
|
|
116
|
+
error_data = response.json()
|
|
117
|
+
error_msg = error_data.get("detail", error_data.get("message", error_msg))
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
raise APIError(error_msg, status_code=response.status_code)
|
|
121
|
+
|
|
122
|
+
return response.json()
|
|
123
|
+
|
|
124
|
+
except httpx.TimeoutException:
|
|
125
|
+
raise APIError("Request timeout", status_code=408)
|
|
126
|
+
except httpx.RequestError as e:
|
|
127
|
+
raise APIError(f"Request failed: {str(e)}")
|
|
128
|
+
|
|
129
|
+
def _get(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
130
|
+
"""Make a GET request"""
|
|
131
|
+
return self._request("GET", path, params=params)
|
|
132
|
+
|
|
133
|
+
def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
134
|
+
"""Make a POST request"""
|
|
135
|
+
return self._request("POST", path, json=json)
|
|
136
|
+
|
|
137
|
+
# -------------------------------------------------------------------------
|
|
138
|
+
# Search Methods
|
|
139
|
+
# -------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
def search(
|
|
142
|
+
self,
|
|
143
|
+
query: str,
|
|
144
|
+
*,
|
|
145
|
+
num: int = 10,
|
|
146
|
+
categories: list[str] | None = None,
|
|
147
|
+
symbols: list[str] | None = None,
|
|
148
|
+
start_date: str | None = None,
|
|
149
|
+
end_date: str | None = None,
|
|
150
|
+
) -> list[dict[str, Any]]:
|
|
151
|
+
"""
|
|
152
|
+
Search documents across all categories
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
query: Search query string
|
|
156
|
+
num: Number of results to return (default: 10, max: 100)
|
|
157
|
+
categories: Filter by document categories
|
|
158
|
+
symbols: Filter by stock symbols (e.g., ["US:AAPL", "HK:0700"])
|
|
159
|
+
start_date: Start date filter (YYYY-MM-DD)
|
|
160
|
+
end_date: End date filter (YYYY-MM-DD)
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
List of matching documents
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
>>> docs = client.search("Tesla earnings", num=10)
|
|
167
|
+
>>> for doc in docs:
|
|
168
|
+
... print(doc["title"])
|
|
169
|
+
"""
|
|
170
|
+
data = {
|
|
171
|
+
"query": query,
|
|
172
|
+
"num": num,
|
|
173
|
+
}
|
|
174
|
+
if categories:
|
|
175
|
+
data["categories"] = categories
|
|
176
|
+
if symbols:
|
|
177
|
+
data["symbols"] = symbols
|
|
178
|
+
if start_date:
|
|
179
|
+
data["start_date"] = start_date
|
|
180
|
+
if end_date:
|
|
181
|
+
data["end_date"] = end_date
|
|
182
|
+
|
|
183
|
+
response = self._post("/v2/search", json=data)
|
|
184
|
+
return response.get("docs", [])
|
|
185
|
+
|
|
186
|
+
def search_news(
|
|
187
|
+
self,
|
|
188
|
+
query: str,
|
|
189
|
+
*,
|
|
190
|
+
num: int = 10,
|
|
191
|
+
symbols: list[str] | None = None,
|
|
192
|
+
start_date: str | None = None,
|
|
193
|
+
end_date: str | None = None,
|
|
194
|
+
) -> list[dict[str, Any]]:
|
|
195
|
+
"""
|
|
196
|
+
Search news articles
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
query: Search query string
|
|
200
|
+
num: Number of results to return
|
|
201
|
+
symbols: Filter by stock symbols
|
|
202
|
+
start_date: Start date filter (YYYY-MM-DD)
|
|
203
|
+
end_date: End date filter (YYYY-MM-DD)
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
List of news articles
|
|
207
|
+
"""
|
|
208
|
+
data = {"query": query, "num": num}
|
|
209
|
+
if symbols:
|
|
210
|
+
data["symbols"] = symbols
|
|
211
|
+
if start_date:
|
|
212
|
+
data["start_date"] = start_date
|
|
213
|
+
if end_date:
|
|
214
|
+
data["end_date"] = end_date
|
|
215
|
+
|
|
216
|
+
response = self._post("/v2/search/news", json=data)
|
|
217
|
+
return response.get("docs", [])
|
|
218
|
+
|
|
219
|
+
def search_reports(
|
|
220
|
+
self,
|
|
221
|
+
query: str,
|
|
222
|
+
*,
|
|
223
|
+
num: int = 10,
|
|
224
|
+
symbols: list[str] | None = None,
|
|
225
|
+
start_date: str | None = None,
|
|
226
|
+
end_date: str | None = None,
|
|
227
|
+
) -> list[dict[str, Any]]:
|
|
228
|
+
"""
|
|
229
|
+
Search research reports
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
query: Search query string
|
|
233
|
+
num: Number of results to return
|
|
234
|
+
symbols: Filter by stock symbols
|
|
235
|
+
start_date: Start date filter (YYYY-MM-DD)
|
|
236
|
+
end_date: End date filter (YYYY-MM-DD)
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
List of research reports
|
|
240
|
+
"""
|
|
241
|
+
data = {"query": query, "num": num}
|
|
242
|
+
if symbols:
|
|
243
|
+
data["symbols"] = symbols
|
|
244
|
+
if start_date:
|
|
245
|
+
data["start_date"] = start_date
|
|
246
|
+
if end_date:
|
|
247
|
+
data["end_date"] = end_date
|
|
248
|
+
|
|
249
|
+
response = self._post("/v2/search/reports", json=data)
|
|
250
|
+
return response.get("docs", [])
|
|
251
|
+
|
|
252
|
+
def search_filings(
|
|
253
|
+
self,
|
|
254
|
+
query: str,
|
|
255
|
+
*,
|
|
256
|
+
num: int = 10,
|
|
257
|
+
symbols: list[str] | None = None,
|
|
258
|
+
start_date: str | None = None,
|
|
259
|
+
end_date: str | None = None,
|
|
260
|
+
) -> list[dict[str, Any]]:
|
|
261
|
+
"""
|
|
262
|
+
Search company filings and announcements
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
query: Search query string
|
|
266
|
+
num: Number of results to return
|
|
267
|
+
symbols: Filter by stock symbols
|
|
268
|
+
start_date: Start date filter (YYYY-MM-DD)
|
|
269
|
+
end_date: End date filter (YYYY-MM-DD)
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
List of filings
|
|
273
|
+
"""
|
|
274
|
+
data = {"query": query, "num": num}
|
|
275
|
+
if symbols:
|
|
276
|
+
data["symbols"] = symbols
|
|
277
|
+
if start_date:
|
|
278
|
+
data["start_date"] = start_date
|
|
279
|
+
if end_date:
|
|
280
|
+
data["end_date"] = end_date
|
|
281
|
+
|
|
282
|
+
response = self._post("/v2/search/filings", json=data)
|
|
283
|
+
return response.get("docs", [])
|
|
284
|
+
|
|
285
|
+
def search_transcripts(
|
|
286
|
+
self,
|
|
287
|
+
query: str,
|
|
288
|
+
*,
|
|
289
|
+
num: int = 10,
|
|
290
|
+
symbols: list[str] | None = None,
|
|
291
|
+
start_date: str | None = None,
|
|
292
|
+
end_date: str | None = None,
|
|
293
|
+
) -> list[dict[str, Any]]:
|
|
294
|
+
"""
|
|
295
|
+
Search earnings call transcripts
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
query: Search query string
|
|
299
|
+
num: Number of results to return
|
|
300
|
+
symbols: Filter by stock symbols
|
|
301
|
+
start_date: Start date filter (YYYY-MM-DD)
|
|
302
|
+
end_date: End date filter (YYYY-MM-DD)
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
List of transcripts
|
|
306
|
+
"""
|
|
307
|
+
data = {"query": query, "num": num}
|
|
308
|
+
if symbols:
|
|
309
|
+
data["symbols"] = symbols
|
|
310
|
+
if start_date:
|
|
311
|
+
data["start_date"] = start_date
|
|
312
|
+
if end_date:
|
|
313
|
+
data["end_date"] = end_date
|
|
314
|
+
|
|
315
|
+
response = self._post("/v2/search/conference-calls", json=data)
|
|
316
|
+
return response.get("docs", [])
|
|
317
|
+
|
|
318
|
+
# -------------------------------------------------------------------------
|
|
319
|
+
# Sub-modules (lazy loading)
|
|
320
|
+
# -------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def stock(self):
|
|
324
|
+
"""Stock data module for financial statements, prices, etc."""
|
|
325
|
+
if self._stock is None:
|
|
326
|
+
from reportify_sdk.stock import StockModule
|
|
327
|
+
self._stock = StockModule(self)
|
|
328
|
+
return self._stock
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def timeline(self):
|
|
332
|
+
"""Timeline module for following companies, topics, etc."""
|
|
333
|
+
if self._timeline is None:
|
|
334
|
+
from reportify_sdk.timeline import TimelineModule
|
|
335
|
+
self._timeline = TimelineModule(self)
|
|
336
|
+
return self._timeline
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def kb(self):
|
|
340
|
+
"""Knowledge base module for searching user uploaded documents"""
|
|
341
|
+
if self._kb is None:
|
|
342
|
+
from reportify_sdk.kb import KBModule
|
|
343
|
+
self._kb = KBModule(self)
|
|
344
|
+
return self._kb
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def docs(self):
|
|
348
|
+
"""Documents module for accessing document content and metadata"""
|
|
349
|
+
if self._docs is None:
|
|
350
|
+
from reportify_sdk.docs import DocsModule
|
|
351
|
+
self._docs = DocsModule(self)
|
|
352
|
+
return self._docs
|
|
353
|
+
|
|
354
|
+
def close(self):
|
|
355
|
+
"""Close the HTTP client"""
|
|
356
|
+
self._client.close()
|
|
357
|
+
|
|
358
|
+
def __enter__(self):
|
|
359
|
+
return self
|
|
360
|
+
|
|
361
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
362
|
+
self.close()
|
reportify_sdk/docs.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Documents Module
|
|
3
|
+
|
|
4
|
+
Provides access to document content, metadata, and summaries.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from reportify_sdk.client import Reportify
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DocsModule:
|
|
14
|
+
"""
|
|
15
|
+
Documents module for accessing document content and metadata
|
|
16
|
+
|
|
17
|
+
Access through the main client:
|
|
18
|
+
>>> client = Reportify(api_key="xxx")
|
|
19
|
+
>>> doc = client.docs.get("doc_id")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, client: "Reportify"):
|
|
23
|
+
self._client = client
|
|
24
|
+
|
|
25
|
+
def _get(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
26
|
+
return self._client._get(path, params=params)
|
|
27
|
+
|
|
28
|
+
def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
29
|
+
return self._client._post(path, json=json)
|
|
30
|
+
|
|
31
|
+
def get(self, doc_id: str) -> dict[str, Any]:
|
|
32
|
+
"""
|
|
33
|
+
Get document content and metadata
|
|
34
|
+
|
|
35
|
+
Retrieves the full content of a document including chunks.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
doc_id: Document ID
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Document dictionary with content and metadata
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
>>> doc = client.docs.get("abc123")
|
|
45
|
+
>>> print(doc["title"])
|
|
46
|
+
>>> print(doc["content"][:500])
|
|
47
|
+
"""
|
|
48
|
+
return self._get(f"/v1/docs/{doc_id}")
|
|
49
|
+
|
|
50
|
+
def summary(self, doc_id: str) -> dict[str, Any]:
|
|
51
|
+
"""
|
|
52
|
+
Get document summary
|
|
53
|
+
|
|
54
|
+
Retrieves the AI-generated summary of a document.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
doc_id: Document ID
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Summary dictionary with title, summary text, and key points
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
>>> summary = client.docs.summary("abc123")
|
|
64
|
+
>>> print(summary["summary"])
|
|
65
|
+
>>> for point in summary.get("key_points", []):
|
|
66
|
+
... print(f"- {point}")
|
|
67
|
+
"""
|
|
68
|
+
return self._get(f"/v1/docs/{doc_id}/summary")
|
|
69
|
+
|
|
70
|
+
def raw_content(self, doc_id: str) -> dict[str, Any]:
|
|
71
|
+
"""
|
|
72
|
+
Get raw document content
|
|
73
|
+
|
|
74
|
+
Retrieves the original content of a document without processing.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
doc_id: Document ID
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Raw content dictionary
|
|
81
|
+
"""
|
|
82
|
+
return self._get(f"/v1/docs/{doc_id}/raw-content")
|
|
83
|
+
|
|
84
|
+
def list(
|
|
85
|
+
self,
|
|
86
|
+
*,
|
|
87
|
+
symbols: list[str] | None = None,
|
|
88
|
+
categories: list[str] | None = None,
|
|
89
|
+
start_date: str | None = None,
|
|
90
|
+
end_date: str | None = None,
|
|
91
|
+
page: int = 1,
|
|
92
|
+
page_size: int = 20,
|
|
93
|
+
) -> dict[str, Any]:
|
|
94
|
+
"""
|
|
95
|
+
List documents with filters
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
symbols: Filter by stock symbols
|
|
99
|
+
categories: Filter by document categories
|
|
100
|
+
start_date: Start date filter (YYYY-MM-DD)
|
|
101
|
+
end_date: End date filter (YYYY-MM-DD)
|
|
102
|
+
page: Page number (default: 1)
|
|
103
|
+
page_size: Number of items per page (default: 20)
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Dictionary with documents list and pagination info
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
>>> result = client.docs.list(symbols=["US:AAPL"], page_size=10)
|
|
110
|
+
>>> for doc in result["docs"]:
|
|
111
|
+
... print(doc["title"])
|
|
112
|
+
"""
|
|
113
|
+
data: dict[str, Any] = {
|
|
114
|
+
"page_num": page,
|
|
115
|
+
"page_size": page_size,
|
|
116
|
+
}
|
|
117
|
+
if symbols:
|
|
118
|
+
data["symbols"] = symbols
|
|
119
|
+
if categories:
|
|
120
|
+
data["categories"] = categories
|
|
121
|
+
if start_date:
|
|
122
|
+
data["start_date"] = start_date
|
|
123
|
+
if end_date:
|
|
124
|
+
data["end_date"] = end_date
|
|
125
|
+
|
|
126
|
+
return self._post("/v1/docs", json=data)
|
|
127
|
+
|
|
128
|
+
def search_chunks(
|
|
129
|
+
self,
|
|
130
|
+
query: str,
|
|
131
|
+
*,
|
|
132
|
+
symbols: list[str] | None = None,
|
|
133
|
+
categories: list[str] | None = None,
|
|
134
|
+
start_date: str | None = None,
|
|
135
|
+
end_date: str | None = None,
|
|
136
|
+
num: int = 10,
|
|
137
|
+
) -> list[dict[str, Any]]:
|
|
138
|
+
"""
|
|
139
|
+
Search document chunks semantically
|
|
140
|
+
|
|
141
|
+
Performs semantic search across document chunks.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
query: Search query string
|
|
145
|
+
symbols: Filter by stock symbols
|
|
146
|
+
categories: Filter by document categories
|
|
147
|
+
start_date: Start date filter (YYYY-MM-DD)
|
|
148
|
+
end_date: End date filter (YYYY-MM-DD)
|
|
149
|
+
num: Number of results to return
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
List of matching chunks with document info
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
>>> chunks = client.docs.search_chunks("revenue guidance", num=5)
|
|
156
|
+
>>> for chunk in chunks:
|
|
157
|
+
... print(chunk["content"])
|
|
158
|
+
"""
|
|
159
|
+
data: dict[str, Any] = {
|
|
160
|
+
"query": query,
|
|
161
|
+
"num": num,
|
|
162
|
+
}
|
|
163
|
+
if symbols:
|
|
164
|
+
data["symbols"] = symbols
|
|
165
|
+
if categories:
|
|
166
|
+
data["categories"] = categories
|
|
167
|
+
if start_date:
|
|
168
|
+
data["start_date"] = start_date
|
|
169
|
+
if end_date:
|
|
170
|
+
data["end_date"] = end_date
|
|
171
|
+
|
|
172
|
+
response = self._post("/v1/search/chunks", json=data)
|
|
173
|
+
return response.get("chunks", [])
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reportify SDK Exceptions
|
|
3
|
+
|
|
4
|
+
Custom exception classes for handling API errors.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ReportifyError(Exception):
|
|
9
|
+
"""Base exception for Reportify SDK"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message: str, status_code: int | None = None):
|
|
12
|
+
self.message = message
|
|
13
|
+
self.status_code = status_code
|
|
14
|
+
super().__init__(self.message)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AuthenticationError(ReportifyError):
|
|
18
|
+
"""Raised when API key is invalid or missing"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, message: str = "Invalid or missing API key"):
|
|
21
|
+
super().__init__(message, status_code=401)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RateLimitError(ReportifyError):
|
|
25
|
+
"""Raised when rate limit is exceeded"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, message: str = "Rate limit exceeded"):
|
|
28
|
+
super().__init__(message, status_code=429)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class NotFoundError(ReportifyError):
|
|
32
|
+
"""Raised when requested resource is not found"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, message: str = "Resource not found"):
|
|
35
|
+
super().__init__(message, status_code=404)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class APIError(ReportifyError):
|
|
39
|
+
"""Raised for general API errors"""
|
|
40
|
+
|
|
41
|
+
pass
|
reportify_sdk/kb.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Knowledge Base Module
|
|
3
|
+
|
|
4
|
+
Provides access to user's personal knowledge base for searching
|
|
5
|
+
uploaded documents and folders.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from reportify_sdk.client import Reportify
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class KBModule:
|
|
15
|
+
"""
|
|
16
|
+
Knowledge base module for searching user uploaded documents
|
|
17
|
+
|
|
18
|
+
Access through the main client:
|
|
19
|
+
>>> client = Reportify(api_key="xxx")
|
|
20
|
+
>>> results = client.kb.search("revenue breakdown")
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, client: "Reportify"):
|
|
24
|
+
self._client = client
|
|
25
|
+
|
|
26
|
+
def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
27
|
+
return self._client._post(path, json=json)
|
|
28
|
+
|
|
29
|
+
def search(
|
|
30
|
+
self,
|
|
31
|
+
query: str,
|
|
32
|
+
*,
|
|
33
|
+
folder_ids: list[str] | None = None,
|
|
34
|
+
doc_ids: list[str] | None = None,
|
|
35
|
+
start_date: str | None = None,
|
|
36
|
+
end_date: str | None = None,
|
|
37
|
+
num: int = 10,
|
|
38
|
+
) -> list[dict[str, Any]]:
|
|
39
|
+
"""
|
|
40
|
+
Search user's knowledge base
|
|
41
|
+
|
|
42
|
+
Performs semantic search across documents the user has uploaded
|
|
43
|
+
to their personal knowledge base.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
query: Search query string
|
|
47
|
+
folder_ids: Filter by specific folder IDs
|
|
48
|
+
doc_ids: Filter by specific document IDs
|
|
49
|
+
start_date: Start date filter (YYYY-MM-DD)
|
|
50
|
+
end_date: End date filter (YYYY-MM-DD)
|
|
51
|
+
num: Number of results to return (default: 10)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
List of matching chunks with document information
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
>>> results = client.kb.search("quarterly revenue", num=5)
|
|
58
|
+
>>> for chunk in results:
|
|
59
|
+
... print(chunk["content"][:100])
|
|
60
|
+
... print(f"From: {chunk['doc']['title']}")
|
|
61
|
+
"""
|
|
62
|
+
data: dict[str, Any] = {
|
|
63
|
+
"query": query,
|
|
64
|
+
"num": num,
|
|
65
|
+
}
|
|
66
|
+
if folder_ids:
|
|
67
|
+
data["folder_ids"] = folder_ids
|
|
68
|
+
if doc_ids:
|
|
69
|
+
data["doc_ids"] = doc_ids
|
|
70
|
+
if start_date:
|
|
71
|
+
data["start_date"] = start_date
|
|
72
|
+
if end_date:
|
|
73
|
+
data["end_date"] = end_date
|
|
74
|
+
|
|
75
|
+
response = self._post("/v1/tools/kb/search", json=data)
|
|
76
|
+
return response.get("chunks", [])
|