reportify-sdk 0.2.9__tar.gz → 0.2.10__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.
- {reportify_sdk-0.2.9/reportify_sdk.egg-info → reportify_sdk-0.2.10}/PKG-INFO +1 -1
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/pyproject.toml +1 -1
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/__init__.py +1 -1
- reportify_sdk-0.2.10/reportify_sdk/client.py +262 -0
- reportify_sdk-0.2.10/reportify_sdk/search.py +357 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10/reportify_sdk.egg-info}/PKG-INFO +1 -1
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk.egg-info/SOURCES.txt +1 -0
- reportify_sdk-0.2.9/reportify_sdk/client.py +0 -580
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/LICENSE +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/README.md +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/agent.py +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/channels.py +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/chat.py +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/concepts.py +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/docs.py +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/exceptions.py +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/kb.py +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/quant.py +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/stock.py +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/timeline.py +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/user.py +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk.egg-info/dependency_links.txt +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk.egg-info/requires.txt +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk.egg-info/top_level.txt +0 -0
- {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/setup.cfg +0 -0
|
@@ -0,0 +1,262 @@
|
|
|
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
|
+
self._quant = None
|
|
65
|
+
self._concepts = None
|
|
66
|
+
self._channels = None
|
|
67
|
+
self._chat = None
|
|
68
|
+
self._agent = None
|
|
69
|
+
self._user = None
|
|
70
|
+
self._search = None
|
|
71
|
+
|
|
72
|
+
def _get_headers(self) -> dict[str, str]:
|
|
73
|
+
"""Get default headers for API requests"""
|
|
74
|
+
return {
|
|
75
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
"User-Agent": "reportify-sdk-python/0.2.10",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def _request(
|
|
81
|
+
self,
|
|
82
|
+
method: str,
|
|
83
|
+
path: str,
|
|
84
|
+
params: dict[str, Any] | None = None,
|
|
85
|
+
json: dict[str, Any] | None = None,
|
|
86
|
+
) -> dict[str, Any]:
|
|
87
|
+
"""
|
|
88
|
+
Make an HTTP request to the API
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
method: HTTP method (GET, POST, etc.)
|
|
92
|
+
path: API endpoint path
|
|
93
|
+
params: Query parameters
|
|
94
|
+
json: JSON body data
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Response data as dictionary
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
AuthenticationError: If API key is invalid
|
|
101
|
+
RateLimitError: If rate limit is exceeded
|
|
102
|
+
NotFoundError: If resource is not found
|
|
103
|
+
APIError: For other API errors
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
response = self._client.request(
|
|
107
|
+
method=method,
|
|
108
|
+
url=path,
|
|
109
|
+
params=params,
|
|
110
|
+
json=json,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Handle error responses
|
|
114
|
+
if response.status_code == 401:
|
|
115
|
+
raise AuthenticationError()
|
|
116
|
+
elif response.status_code == 429:
|
|
117
|
+
raise RateLimitError()
|
|
118
|
+
elif response.status_code == 404:
|
|
119
|
+
raise NotFoundError()
|
|
120
|
+
elif response.status_code >= 400:
|
|
121
|
+
error_msg = response.text
|
|
122
|
+
try:
|
|
123
|
+
error_data = response.json()
|
|
124
|
+
error_msg = error_data.get("detail", error_data.get("message", error_msg))
|
|
125
|
+
except Exception:
|
|
126
|
+
pass
|
|
127
|
+
raise APIError(error_msg, status_code=response.status_code)
|
|
128
|
+
|
|
129
|
+
return response.json()
|
|
130
|
+
|
|
131
|
+
except httpx.TimeoutException:
|
|
132
|
+
raise APIError("Request timeout", status_code=408)
|
|
133
|
+
except httpx.RequestError as e:
|
|
134
|
+
raise APIError(f"Request failed: {str(e)}")
|
|
135
|
+
|
|
136
|
+
def _get(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
137
|
+
"""Make a GET request"""
|
|
138
|
+
return self._request("GET", path, params=params)
|
|
139
|
+
|
|
140
|
+
def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
141
|
+
"""Make a POST request"""
|
|
142
|
+
return self._request("POST", path, json=json)
|
|
143
|
+
|
|
144
|
+
def _get_bytes(self, path: str) -> bytes:
|
|
145
|
+
"""Make a GET request and return raw bytes (for file downloads)"""
|
|
146
|
+
try:
|
|
147
|
+
response = self._client.get(path)
|
|
148
|
+
response.raise_for_status()
|
|
149
|
+
return response.content
|
|
150
|
+
except httpx.HTTPStatusError as e:
|
|
151
|
+
status_code = e.response.status_code
|
|
152
|
+
if status_code == 401:
|
|
153
|
+
raise AuthenticationError("Invalid API key")
|
|
154
|
+
elif status_code == 404:
|
|
155
|
+
raise NotFoundError(f"Resource not found: {path}")
|
|
156
|
+
elif status_code == 429:
|
|
157
|
+
raise RateLimitError("Rate limit exceeded")
|
|
158
|
+
else:
|
|
159
|
+
raise APIError(f"API error: {status_code}")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# -------------------------------------------------------------------------
|
|
163
|
+
# Sub-modules (lazy loading)
|
|
164
|
+
# -------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def search(self):
|
|
168
|
+
"""Search module for document search across all categories"""
|
|
169
|
+
if self._search is None:
|
|
170
|
+
from reportify_sdk.search import SearchModule
|
|
171
|
+
self._search = SearchModule(self)
|
|
172
|
+
return self._search
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def stock(self):
|
|
176
|
+
"""Stock data module for financial statements, prices, etc."""
|
|
177
|
+
if self._stock is None:
|
|
178
|
+
from reportify_sdk.stock import StockModule
|
|
179
|
+
self._stock = StockModule(self)
|
|
180
|
+
return self._stock
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def timeline(self):
|
|
184
|
+
"""Timeline module for following companies, topics, etc."""
|
|
185
|
+
if self._timeline is None:
|
|
186
|
+
from reportify_sdk.timeline import TimelineModule
|
|
187
|
+
self._timeline = TimelineModule(self)
|
|
188
|
+
return self._timeline
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def kb(self):
|
|
192
|
+
"""Knowledge base module for searching user uploaded documents"""
|
|
193
|
+
if self._kb is None:
|
|
194
|
+
from reportify_sdk.kb import KBModule
|
|
195
|
+
self._kb = KBModule(self)
|
|
196
|
+
return self._kb
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def docs(self):
|
|
200
|
+
"""Documents module for accessing document content and metadata"""
|
|
201
|
+
if self._docs is None:
|
|
202
|
+
from reportify_sdk.docs import DocsModule
|
|
203
|
+
self._docs = DocsModule(self)
|
|
204
|
+
return self._docs
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def quant(self):
|
|
208
|
+
"""Quant module for indicators, factors, quotes, and backtesting"""
|
|
209
|
+
if self._quant is None:
|
|
210
|
+
from reportify_sdk.quant import QuantModule
|
|
211
|
+
self._quant = QuantModule(self)
|
|
212
|
+
return self._quant
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def concepts(self):
|
|
216
|
+
"""Concepts module for accessing concept data and feeds"""
|
|
217
|
+
if self._concepts is None:
|
|
218
|
+
from reportify_sdk.concepts import ConceptsModule
|
|
219
|
+
self._concepts = ConceptsModule(self)
|
|
220
|
+
return self._concepts
|
|
221
|
+
|
|
222
|
+
@property
|
|
223
|
+
def channels(self):
|
|
224
|
+
"""Channels module for searching and following channels"""
|
|
225
|
+
if self._channels is None:
|
|
226
|
+
from reportify_sdk.channels import ChannelsModule
|
|
227
|
+
self._channels = ChannelsModule(self)
|
|
228
|
+
return self._channels
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def chat(self):
|
|
232
|
+
"""Chat module for document-based Q&A"""
|
|
233
|
+
if self._chat is None:
|
|
234
|
+
from reportify_sdk.chat import ChatModule
|
|
235
|
+
self._chat = ChatModule(self)
|
|
236
|
+
return self._chat
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def agent(self):
|
|
240
|
+
"""Agent module for AI-powered conversations and workflows"""
|
|
241
|
+
if self._agent is None:
|
|
242
|
+
from reportify_sdk.agent import AgentModule
|
|
243
|
+
self._agent = AgentModule(self)
|
|
244
|
+
return self._agent
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def user(self):
|
|
248
|
+
"""User module for user-related tools and data"""
|
|
249
|
+
if self._user is None:
|
|
250
|
+
from reportify_sdk.user import UserModule
|
|
251
|
+
self._user = UserModule(self)
|
|
252
|
+
return self._user
|
|
253
|
+
|
|
254
|
+
def close(self):
|
|
255
|
+
"""Close the HTTP client"""
|
|
256
|
+
self._client.close()
|
|
257
|
+
|
|
258
|
+
def __enter__(self):
|
|
259
|
+
return self
|
|
260
|
+
|
|
261
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
262
|
+
self.close()
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Search Module
|
|
3
|
+
|
|
4
|
+
Provides document search functionality across various categories.
|
|
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 SearchModule:
|
|
14
|
+
"""
|
|
15
|
+
Search module for document search functionality.
|
|
16
|
+
|
|
17
|
+
Access via client.search:
|
|
18
|
+
>>> results = client.search.all("Tesla earnings")
|
|
19
|
+
>>> news = client.search.news("Apple iPhone")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, client: "Reportify"):
|
|
23
|
+
self._client = client
|
|
24
|
+
|
|
25
|
+
def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
26
|
+
return self._client._post(path, json=json)
|
|
27
|
+
|
|
28
|
+
def all(
|
|
29
|
+
self,
|
|
30
|
+
query: str,
|
|
31
|
+
*,
|
|
32
|
+
num: int = 10,
|
|
33
|
+
categories: list[str] | None = None,
|
|
34
|
+
symbols: list[str] | None = None,
|
|
35
|
+
industries: list[str] | None = None,
|
|
36
|
+
channel_ids: list[str] | None = None,
|
|
37
|
+
start_datetime: str | None = None,
|
|
38
|
+
end_datetime: str | None = None,
|
|
39
|
+
) -> list[dict[str, Any]]:
|
|
40
|
+
"""
|
|
41
|
+
Search documents across all categories
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
query: Search query string
|
|
45
|
+
num: Number of results to return (default: 10, max: 100)
|
|
46
|
+
categories: Filter by categories (news, reports, filings, transcripts, socials)
|
|
47
|
+
symbols: Filter by stock symbols (e.g., ["US:AAPL", "HK:0700"])
|
|
48
|
+
industries: Filter by industries
|
|
49
|
+
channel_ids: Filter by channel IDs
|
|
50
|
+
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
51
|
+
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
List of matching documents
|
|
55
|
+
"""
|
|
56
|
+
data: dict[str, Any] = {"query": query, "num": num}
|
|
57
|
+
if categories:
|
|
58
|
+
data["categories"] = categories
|
|
59
|
+
if symbols:
|
|
60
|
+
data["symbols"] = symbols
|
|
61
|
+
if industries:
|
|
62
|
+
data["industries"] = industries
|
|
63
|
+
if channel_ids:
|
|
64
|
+
data["channel_ids"] = channel_ids
|
|
65
|
+
if start_datetime:
|
|
66
|
+
data["start_datetime"] = start_datetime
|
|
67
|
+
if end_datetime:
|
|
68
|
+
data["end_datetime"] = end_datetime
|
|
69
|
+
|
|
70
|
+
response = self._post("/v2/search", json=data)
|
|
71
|
+
return response.get("docs", [])
|
|
72
|
+
|
|
73
|
+
def news(
|
|
74
|
+
self,
|
|
75
|
+
query: str,
|
|
76
|
+
*,
|
|
77
|
+
num: int = 10,
|
|
78
|
+
symbols: list[str] | None = None,
|
|
79
|
+
channel_ids: list[str] | None = None,
|
|
80
|
+
start_datetime: str | None = None,
|
|
81
|
+
end_datetime: str | None = None,
|
|
82
|
+
) -> list[dict[str, Any]]:
|
|
83
|
+
"""
|
|
84
|
+
Search news articles
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
query: Search query string
|
|
88
|
+
num: Number of results to return
|
|
89
|
+
symbols: Filter by stock symbols
|
|
90
|
+
channel_ids: Filter by channel IDs
|
|
91
|
+
start_datetime: Start datetime filter
|
|
92
|
+
end_datetime: End datetime filter
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
List of news documents
|
|
96
|
+
"""
|
|
97
|
+
data: dict[str, Any] = {"query": query, "num": num}
|
|
98
|
+
if symbols:
|
|
99
|
+
data["symbols"] = symbols
|
|
100
|
+
if channel_ids:
|
|
101
|
+
data["channel_ids"] = channel_ids
|
|
102
|
+
if start_datetime:
|
|
103
|
+
data["start_datetime"] = start_datetime
|
|
104
|
+
if end_datetime:
|
|
105
|
+
data["end_datetime"] = end_datetime
|
|
106
|
+
|
|
107
|
+
response = self._post("/v2/search/news", json=data)
|
|
108
|
+
return response.get("docs", [])
|
|
109
|
+
|
|
110
|
+
def reports(
|
|
111
|
+
self,
|
|
112
|
+
query: str,
|
|
113
|
+
*,
|
|
114
|
+
num: int = 10,
|
|
115
|
+
symbols: list[str] | None = None,
|
|
116
|
+
industries: list[str] | None = None,
|
|
117
|
+
channel_ids: list[str] | None = None,
|
|
118
|
+
start_datetime: str | None = None,
|
|
119
|
+
end_datetime: str | None = None,
|
|
120
|
+
) -> list[dict[str, Any]]:
|
|
121
|
+
"""
|
|
122
|
+
Search research reports
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
query: Search query string
|
|
126
|
+
num: Number of results to return
|
|
127
|
+
symbols: Filter by stock symbols
|
|
128
|
+
industries: Filter by industries
|
|
129
|
+
channel_ids: Filter by channel IDs
|
|
130
|
+
start_datetime: Start datetime filter
|
|
131
|
+
end_datetime: End datetime filter
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
List of research reports
|
|
135
|
+
"""
|
|
136
|
+
data: dict[str, Any] = {"query": query, "num": num}
|
|
137
|
+
if symbols:
|
|
138
|
+
data["symbols"] = symbols
|
|
139
|
+
if industries:
|
|
140
|
+
data["industries"] = industries
|
|
141
|
+
if channel_ids:
|
|
142
|
+
data["channel_ids"] = channel_ids
|
|
143
|
+
if start_datetime:
|
|
144
|
+
data["start_datetime"] = start_datetime
|
|
145
|
+
if end_datetime:
|
|
146
|
+
data["end_datetime"] = end_datetime
|
|
147
|
+
|
|
148
|
+
response = self._post("/v2/search/reports", json=data)
|
|
149
|
+
return response.get("docs", [])
|
|
150
|
+
|
|
151
|
+
def filings(
|
|
152
|
+
self,
|
|
153
|
+
query: str,
|
|
154
|
+
symbols: list[str],
|
|
155
|
+
*,
|
|
156
|
+
num: int = 10,
|
|
157
|
+
start_datetime: str | None = None,
|
|
158
|
+
end_datetime: str | None = None,
|
|
159
|
+
) -> list[dict[str, Any]]:
|
|
160
|
+
"""
|
|
161
|
+
Search company filings
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
query: Search query string
|
|
165
|
+
symbols: Stock symbols to filter by (required)
|
|
166
|
+
num: Number of results to return
|
|
167
|
+
start_datetime: Start datetime filter
|
|
168
|
+
end_datetime: End datetime filter
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
List of filing documents
|
|
172
|
+
"""
|
|
173
|
+
data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
|
|
174
|
+
if start_datetime:
|
|
175
|
+
data["start_datetime"] = start_datetime
|
|
176
|
+
if end_datetime:
|
|
177
|
+
data["end_datetime"] = end_datetime
|
|
178
|
+
|
|
179
|
+
response = self._post("/v2/search/filings", json=data)
|
|
180
|
+
return response.get("docs", [])
|
|
181
|
+
|
|
182
|
+
def conference_calls(
|
|
183
|
+
self,
|
|
184
|
+
query: str,
|
|
185
|
+
symbols: list[str],
|
|
186
|
+
*,
|
|
187
|
+
num: int = 10,
|
|
188
|
+
start_datetime: str | None = None,
|
|
189
|
+
end_datetime: str | None = None,
|
|
190
|
+
fiscal_year: str | None = None,
|
|
191
|
+
fiscal_quarter: str | None = None,
|
|
192
|
+
) -> list[dict[str, Any]]:
|
|
193
|
+
"""
|
|
194
|
+
Search earnings call transcripts and slides
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
query: Search query string
|
|
198
|
+
symbols: Stock symbols to filter by (required)
|
|
199
|
+
num: Number of results to return
|
|
200
|
+
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
201
|
+
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
202
|
+
fiscal_year: Fiscal year filter (e.g., '2025', '2026')
|
|
203
|
+
fiscal_quarter: Fiscal quarter filter (e.g., 'Q1', 'Q2', 'Q3', 'Q4')
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
List of conference call documents
|
|
207
|
+
"""
|
|
208
|
+
data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
|
|
209
|
+
if start_datetime:
|
|
210
|
+
data["start_datetime"] = start_datetime
|
|
211
|
+
if end_datetime:
|
|
212
|
+
data["end_datetime"] = end_datetime
|
|
213
|
+
if fiscal_year:
|
|
214
|
+
data["fiscal_year"] = fiscal_year
|
|
215
|
+
if fiscal_quarter:
|
|
216
|
+
data["fiscal_quarter"] = fiscal_quarter
|
|
217
|
+
|
|
218
|
+
response = self._post("/v2/search/conference-calls", json=data)
|
|
219
|
+
return response.get("docs", [])
|
|
220
|
+
|
|
221
|
+
def earnings_pack(
|
|
222
|
+
self,
|
|
223
|
+
query: str,
|
|
224
|
+
symbols: list[str],
|
|
225
|
+
*,
|
|
226
|
+
num: int = 10,
|
|
227
|
+
start_datetime: str | None = None,
|
|
228
|
+
end_datetime: str | None = None,
|
|
229
|
+
fiscal_year: str | None = None,
|
|
230
|
+
fiscal_quarter: str | None = None,
|
|
231
|
+
) -> list[dict[str, Any]]:
|
|
232
|
+
"""
|
|
233
|
+
Search earnings financial reports
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
query: Search query string
|
|
237
|
+
symbols: Stock symbols to filter by (required)
|
|
238
|
+
num: Number of results to return
|
|
239
|
+
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
240
|
+
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
241
|
+
fiscal_year: Fiscal year filter (e.g., '2025', '2026')
|
|
242
|
+
fiscal_quarter: Fiscal quarter filter (e.g., 'Q1', 'Q2', 'Q3', 'Q4')
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
List of earnings pack documents
|
|
246
|
+
"""
|
|
247
|
+
data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
|
|
248
|
+
if start_datetime:
|
|
249
|
+
data["start_datetime"] = start_datetime
|
|
250
|
+
if end_datetime:
|
|
251
|
+
data["end_datetime"] = end_datetime
|
|
252
|
+
if fiscal_year:
|
|
253
|
+
data["fiscal_year"] = fiscal_year
|
|
254
|
+
if fiscal_quarter:
|
|
255
|
+
data["fiscal_quarter"] = fiscal_quarter
|
|
256
|
+
|
|
257
|
+
response = self._post("/v2/search/earnings-pack", json=data)
|
|
258
|
+
return response.get("docs", [])
|
|
259
|
+
|
|
260
|
+
def minutes(
|
|
261
|
+
self,
|
|
262
|
+
query: str,
|
|
263
|
+
*,
|
|
264
|
+
num: int = 10,
|
|
265
|
+
symbols: list[str] | None = None,
|
|
266
|
+
start_datetime: str | None = None,
|
|
267
|
+
end_datetime: str | None = None,
|
|
268
|
+
) -> list[dict[str, Any]]:
|
|
269
|
+
"""
|
|
270
|
+
Search conference calls and IR (Investor Relations) meetings
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
query: Search query string
|
|
274
|
+
num: Number of results to return
|
|
275
|
+
symbols: Filter by stock symbols
|
|
276
|
+
start_datetime: Start datetime filter
|
|
277
|
+
end_datetime: End datetime filter
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
List of minutes documents
|
|
281
|
+
"""
|
|
282
|
+
data: dict[str, Any] = {"query": query, "num": num}
|
|
283
|
+
if symbols:
|
|
284
|
+
data["symbols"] = symbols
|
|
285
|
+
if start_datetime:
|
|
286
|
+
data["start_datetime"] = start_datetime
|
|
287
|
+
if end_datetime:
|
|
288
|
+
data["end_datetime"] = end_datetime
|
|
289
|
+
|
|
290
|
+
response = self._post("/v2/search/minutes", json=data)
|
|
291
|
+
return response.get("docs", [])
|
|
292
|
+
|
|
293
|
+
def socials(
|
|
294
|
+
self,
|
|
295
|
+
query: str,
|
|
296
|
+
*,
|
|
297
|
+
num: int = 10,
|
|
298
|
+
symbols: list[str] | None = None,
|
|
299
|
+
channel_ids: list[str] | None = None,
|
|
300
|
+
start_datetime: str | None = None,
|
|
301
|
+
end_datetime: str | None = None,
|
|
302
|
+
) -> list[dict[str, Any]]:
|
|
303
|
+
"""
|
|
304
|
+
Search social media content
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
query: Search query string
|
|
308
|
+
num: Number of results to return
|
|
309
|
+
symbols: Filter by stock symbols
|
|
310
|
+
channel_ids: Filter by channel IDs
|
|
311
|
+
start_datetime: Start datetime filter
|
|
312
|
+
end_datetime: End datetime filter
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
List of social media documents
|
|
316
|
+
"""
|
|
317
|
+
data: dict[str, Any] = {"query": query, "num": num}
|
|
318
|
+
if symbols:
|
|
319
|
+
data["symbols"] = symbols
|
|
320
|
+
if channel_ids:
|
|
321
|
+
data["channel_ids"] = channel_ids
|
|
322
|
+
if start_datetime:
|
|
323
|
+
data["start_datetime"] = start_datetime
|
|
324
|
+
if end_datetime:
|
|
325
|
+
data["end_datetime"] = end_datetime
|
|
326
|
+
|
|
327
|
+
response = self._post("/v2/search/socials", json=data)
|
|
328
|
+
return response.get("docs", [])
|
|
329
|
+
|
|
330
|
+
def webpages(
|
|
331
|
+
self,
|
|
332
|
+
query: str,
|
|
333
|
+
*,
|
|
334
|
+
num: int = 10,
|
|
335
|
+
start_datetime: str | None = None,
|
|
336
|
+
end_datetime: str | None = None,
|
|
337
|
+
) -> list[dict[str, Any]]:
|
|
338
|
+
"""
|
|
339
|
+
Search webpage content
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
query: Search query string
|
|
343
|
+
num: Number of results to return
|
|
344
|
+
start_datetime: Start datetime filter
|
|
345
|
+
end_datetime: End datetime filter
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
List of webpage content
|
|
349
|
+
"""
|
|
350
|
+
data: dict[str, Any] = {"query": query, "num": num}
|
|
351
|
+
if start_datetime:
|
|
352
|
+
data["start_datetime"] = start_datetime
|
|
353
|
+
if end_datetime:
|
|
354
|
+
data["end_datetime"] = end_datetime
|
|
355
|
+
|
|
356
|
+
response = self._post("/v2/search/webpages", json=data)
|
|
357
|
+
return response.get("docs", [])
|
|
@@ -1,580 +0,0 @@
|
|
|
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
|
-
self._quant = None
|
|
65
|
-
self._concepts = None
|
|
66
|
-
self._channels = None
|
|
67
|
-
self._chat = None
|
|
68
|
-
self._agent = None
|
|
69
|
-
self._user = None
|
|
70
|
-
|
|
71
|
-
def _get_headers(self) -> dict[str, str]:
|
|
72
|
-
"""Get default headers for API requests"""
|
|
73
|
-
return {
|
|
74
|
-
"Authorization": f"Bearer {self.api_key}",
|
|
75
|
-
"Content-Type": "application/json",
|
|
76
|
-
"User-Agent": "reportify-sdk-python/0.2.9",
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
def _request(
|
|
80
|
-
self,
|
|
81
|
-
method: str,
|
|
82
|
-
path: str,
|
|
83
|
-
params: dict[str, Any] | None = None,
|
|
84
|
-
json: dict[str, Any] | None = None,
|
|
85
|
-
) -> dict[str, Any]:
|
|
86
|
-
"""
|
|
87
|
-
Make an HTTP request to the API
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
method: HTTP method (GET, POST, etc.)
|
|
91
|
-
path: API endpoint path
|
|
92
|
-
params: Query parameters
|
|
93
|
-
json: JSON body data
|
|
94
|
-
|
|
95
|
-
Returns:
|
|
96
|
-
Response data as dictionary
|
|
97
|
-
|
|
98
|
-
Raises:
|
|
99
|
-
AuthenticationError: If API key is invalid
|
|
100
|
-
RateLimitError: If rate limit is exceeded
|
|
101
|
-
NotFoundError: If resource is not found
|
|
102
|
-
APIError: For other API errors
|
|
103
|
-
"""
|
|
104
|
-
try:
|
|
105
|
-
response = self._client.request(
|
|
106
|
-
method=method,
|
|
107
|
-
url=path,
|
|
108
|
-
params=params,
|
|
109
|
-
json=json,
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
# Handle error responses
|
|
113
|
-
if response.status_code == 401:
|
|
114
|
-
raise AuthenticationError()
|
|
115
|
-
elif response.status_code == 429:
|
|
116
|
-
raise RateLimitError()
|
|
117
|
-
elif response.status_code == 404:
|
|
118
|
-
raise NotFoundError()
|
|
119
|
-
elif response.status_code >= 400:
|
|
120
|
-
error_msg = response.text
|
|
121
|
-
try:
|
|
122
|
-
error_data = response.json()
|
|
123
|
-
error_msg = error_data.get("detail", error_data.get("message", error_msg))
|
|
124
|
-
except Exception:
|
|
125
|
-
pass
|
|
126
|
-
raise APIError(error_msg, status_code=response.status_code)
|
|
127
|
-
|
|
128
|
-
return response.json()
|
|
129
|
-
|
|
130
|
-
except httpx.TimeoutException:
|
|
131
|
-
raise APIError("Request timeout", status_code=408)
|
|
132
|
-
except httpx.RequestError as e:
|
|
133
|
-
raise APIError(f"Request failed: {str(e)}")
|
|
134
|
-
|
|
135
|
-
def _get(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
136
|
-
"""Make a GET request"""
|
|
137
|
-
return self._request("GET", path, params=params)
|
|
138
|
-
|
|
139
|
-
def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
140
|
-
"""Make a POST request"""
|
|
141
|
-
return self._request("POST", path, json=json)
|
|
142
|
-
|
|
143
|
-
def _get_bytes(self, path: str) -> bytes:
|
|
144
|
-
"""Make a GET request and return raw bytes (for file downloads)"""
|
|
145
|
-
try:
|
|
146
|
-
response = self._client.get(path)
|
|
147
|
-
response.raise_for_status()
|
|
148
|
-
return response.content
|
|
149
|
-
except httpx.HTTPStatusError as e:
|
|
150
|
-
status_code = e.response.status_code
|
|
151
|
-
if status_code == 401:
|
|
152
|
-
raise AuthenticationError("Invalid API key")
|
|
153
|
-
elif status_code == 404:
|
|
154
|
-
raise NotFoundError(f"Resource not found: {path}")
|
|
155
|
-
elif status_code == 429:
|
|
156
|
-
raise RateLimitError("Rate limit exceeded")
|
|
157
|
-
else:
|
|
158
|
-
raise APIError(f"API error: {status_code}")
|
|
159
|
-
|
|
160
|
-
# -------------------------------------------------------------------------
|
|
161
|
-
# Search Methods
|
|
162
|
-
# -------------------------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
def search(
|
|
165
|
-
self,
|
|
166
|
-
query: str,
|
|
167
|
-
*,
|
|
168
|
-
num: int = 10,
|
|
169
|
-
symbols: list[str] | None = None,
|
|
170
|
-
industries: list[str] | None = None,
|
|
171
|
-
channel_ids: list[str] | None = None,
|
|
172
|
-
start_datetime: str | None = None,
|
|
173
|
-
end_datetime: str | None = None,
|
|
174
|
-
) -> list[dict[str, Any]]:
|
|
175
|
-
"""
|
|
176
|
-
Search documents across all categories
|
|
177
|
-
|
|
178
|
-
Args:
|
|
179
|
-
query: Search query string
|
|
180
|
-
num: Number of results to return (default: 10, max: 100)
|
|
181
|
-
symbols: Filter by stock symbols (e.g., ["US:AAPL", "HK:0700"])
|
|
182
|
-
industries: Filter by industries
|
|
183
|
-
channel_ids: Filter by channel IDs
|
|
184
|
-
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
185
|
-
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
186
|
-
|
|
187
|
-
Returns:
|
|
188
|
-
List of matching documents
|
|
189
|
-
|
|
190
|
-
Example:
|
|
191
|
-
>>> docs = client.search("Tesla earnings", num=10)
|
|
192
|
-
>>> for doc in docs:
|
|
193
|
-
... print(doc["title"])
|
|
194
|
-
"""
|
|
195
|
-
data: dict[str, Any] = {
|
|
196
|
-
"query": query,
|
|
197
|
-
"num": num,
|
|
198
|
-
}
|
|
199
|
-
if symbols:
|
|
200
|
-
data["symbols"] = symbols
|
|
201
|
-
if industries:
|
|
202
|
-
data["industries"] = industries
|
|
203
|
-
if channel_ids:
|
|
204
|
-
data["channel_ids"] = channel_ids
|
|
205
|
-
if start_datetime:
|
|
206
|
-
data["start_datetime"] = start_datetime
|
|
207
|
-
if end_datetime:
|
|
208
|
-
data["end_datetime"] = end_datetime
|
|
209
|
-
|
|
210
|
-
response = self._post("/v2/search", json=data)
|
|
211
|
-
return response.get("docs", [])
|
|
212
|
-
|
|
213
|
-
def search_news(
|
|
214
|
-
self,
|
|
215
|
-
query: str,
|
|
216
|
-
*,
|
|
217
|
-
num: int = 10,
|
|
218
|
-
symbols: list[str] | None = None,
|
|
219
|
-
channel_ids: list[str] | None = None,
|
|
220
|
-
start_datetime: str | None = None,
|
|
221
|
-
end_datetime: str | None = None,
|
|
222
|
-
) -> list[dict[str, Any]]:
|
|
223
|
-
"""
|
|
224
|
-
Search news articles
|
|
225
|
-
|
|
226
|
-
Args:
|
|
227
|
-
query: Search query string
|
|
228
|
-
num: Number of results to return
|
|
229
|
-
symbols: Filter by stock symbols
|
|
230
|
-
channel_ids: Filter by channel IDs
|
|
231
|
-
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
232
|
-
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
233
|
-
|
|
234
|
-
Returns:
|
|
235
|
-
List of news articles
|
|
236
|
-
"""
|
|
237
|
-
data: dict[str, Any] = {"query": query, "num": num}
|
|
238
|
-
if symbols:
|
|
239
|
-
data["symbols"] = symbols
|
|
240
|
-
if channel_ids:
|
|
241
|
-
data["channel_ids"] = channel_ids
|
|
242
|
-
if start_datetime:
|
|
243
|
-
data["start_datetime"] = start_datetime
|
|
244
|
-
if end_datetime:
|
|
245
|
-
data["end_datetime"] = end_datetime
|
|
246
|
-
|
|
247
|
-
response = self._post("/v2/search/news", json=data)
|
|
248
|
-
return response.get("docs", [])
|
|
249
|
-
|
|
250
|
-
def search_reports(
|
|
251
|
-
self,
|
|
252
|
-
query: str,
|
|
253
|
-
*,
|
|
254
|
-
num: int = 10,
|
|
255
|
-
symbols: list[str] | None = None,
|
|
256
|
-
industries: list[str] | None = None,
|
|
257
|
-
channel_ids: list[str] | None = None,
|
|
258
|
-
start_datetime: str | None = None,
|
|
259
|
-
end_datetime: str | None = None,
|
|
260
|
-
) -> list[dict[str, Any]]:
|
|
261
|
-
"""
|
|
262
|
-
Search research reports
|
|
263
|
-
|
|
264
|
-
Args:
|
|
265
|
-
query: Search query string
|
|
266
|
-
num: Number of results to return
|
|
267
|
-
symbols: Filter by stock symbols
|
|
268
|
-
industries: Filter by industries
|
|
269
|
-
channel_ids: Filter by channel IDs
|
|
270
|
-
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
271
|
-
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
272
|
-
|
|
273
|
-
Returns:
|
|
274
|
-
List of research reports
|
|
275
|
-
"""
|
|
276
|
-
data: dict[str, Any] = {"query": query, "num": num}
|
|
277
|
-
if symbols:
|
|
278
|
-
data["symbols"] = symbols
|
|
279
|
-
if industries:
|
|
280
|
-
data["industries"] = industries
|
|
281
|
-
if channel_ids:
|
|
282
|
-
data["channel_ids"] = channel_ids
|
|
283
|
-
if start_datetime:
|
|
284
|
-
data["start_datetime"] = start_datetime
|
|
285
|
-
if end_datetime:
|
|
286
|
-
data["end_datetime"] = end_datetime
|
|
287
|
-
|
|
288
|
-
response = self._post("/v2/search/reports", json=data)
|
|
289
|
-
return response.get("docs", [])
|
|
290
|
-
|
|
291
|
-
def search_filings(
|
|
292
|
-
self,
|
|
293
|
-
query: str,
|
|
294
|
-
symbols: list[str],
|
|
295
|
-
*,
|
|
296
|
-
num: int = 10,
|
|
297
|
-
start_datetime: str | None = None,
|
|
298
|
-
end_datetime: str | None = None,
|
|
299
|
-
) -> list[dict[str, Any]]:
|
|
300
|
-
"""
|
|
301
|
-
Search company filings and announcements
|
|
302
|
-
|
|
303
|
-
Args:
|
|
304
|
-
query: Search query string
|
|
305
|
-
symbols: Stock symbols to filter by (required)
|
|
306
|
-
num: Number of results to return
|
|
307
|
-
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
308
|
-
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
309
|
-
|
|
310
|
-
Returns:
|
|
311
|
-
List of filings
|
|
312
|
-
"""
|
|
313
|
-
data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
|
|
314
|
-
if start_datetime:
|
|
315
|
-
data["start_datetime"] = start_datetime
|
|
316
|
-
if end_datetime:
|
|
317
|
-
data["end_datetime"] = end_datetime
|
|
318
|
-
|
|
319
|
-
response = self._post("/v2/search/filings", json=data)
|
|
320
|
-
return response.get("docs", [])
|
|
321
|
-
|
|
322
|
-
def search_transcripts(
|
|
323
|
-
self,
|
|
324
|
-
query: str,
|
|
325
|
-
symbols: list[str],
|
|
326
|
-
*,
|
|
327
|
-
num: int = 10,
|
|
328
|
-
start_datetime: str | None = None,
|
|
329
|
-
end_datetime: str | None = None,
|
|
330
|
-
) -> list[dict[str, Any]]:
|
|
331
|
-
"""
|
|
332
|
-
Search earnings call transcripts (conference calls)
|
|
333
|
-
|
|
334
|
-
Args:
|
|
335
|
-
query: Search query string
|
|
336
|
-
symbols: Stock symbols to filter by (required)
|
|
337
|
-
num: Number of results to return
|
|
338
|
-
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
339
|
-
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
340
|
-
|
|
341
|
-
Returns:
|
|
342
|
-
List of transcripts
|
|
343
|
-
"""
|
|
344
|
-
data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
|
|
345
|
-
if start_datetime:
|
|
346
|
-
data["start_datetime"] = start_datetime
|
|
347
|
-
if end_datetime:
|
|
348
|
-
data["end_datetime"] = end_datetime
|
|
349
|
-
|
|
350
|
-
response = self._post("/v2/search/conference-calls", json=data)
|
|
351
|
-
return response.get("docs", [])
|
|
352
|
-
|
|
353
|
-
def search_earnings_pack(
|
|
354
|
-
self,
|
|
355
|
-
query: str,
|
|
356
|
-
symbols: list[str],
|
|
357
|
-
*,
|
|
358
|
-
num: int = 10,
|
|
359
|
-
start_datetime: str | None = None,
|
|
360
|
-
end_datetime: str | None = None,
|
|
361
|
-
) -> list[dict[str, Any]]:
|
|
362
|
-
"""
|
|
363
|
-
Search earnings pack documents
|
|
364
|
-
|
|
365
|
-
Includes financial reports, earnings call transcripts,
|
|
366
|
-
presentation materials, and press releases.
|
|
367
|
-
|
|
368
|
-
Args:
|
|
369
|
-
query: Search query string
|
|
370
|
-
symbols: Stock symbols to filter by (required)
|
|
371
|
-
num: Number of results to return
|
|
372
|
-
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
373
|
-
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
374
|
-
|
|
375
|
-
Returns:
|
|
376
|
-
List of earnings pack documents
|
|
377
|
-
"""
|
|
378
|
-
data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
|
|
379
|
-
if start_datetime:
|
|
380
|
-
data["start_datetime"] = start_datetime
|
|
381
|
-
if end_datetime:
|
|
382
|
-
data["end_datetime"] = end_datetime
|
|
383
|
-
|
|
384
|
-
response = self._post("/v2/search/earnings-pack", json=data)
|
|
385
|
-
return response.get("docs", [])
|
|
386
|
-
|
|
387
|
-
def search_minutes(
|
|
388
|
-
self,
|
|
389
|
-
query: str,
|
|
390
|
-
*,
|
|
391
|
-
num: int = 10,
|
|
392
|
-
symbols: list[str] | None = None,
|
|
393
|
-
start_datetime: str | None = None,
|
|
394
|
-
end_datetime: str | None = None,
|
|
395
|
-
) -> list[dict[str, Any]]:
|
|
396
|
-
"""
|
|
397
|
-
Search minutes transcripts
|
|
398
|
-
|
|
399
|
-
Includes conference calls and IR (Investor Relations) meetings.
|
|
400
|
-
|
|
401
|
-
Args:
|
|
402
|
-
query: Search query string
|
|
403
|
-
num: Number of results to return
|
|
404
|
-
symbols: Filter by stock symbols
|
|
405
|
-
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
406
|
-
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
407
|
-
|
|
408
|
-
Returns:
|
|
409
|
-
List of minutes transcripts
|
|
410
|
-
"""
|
|
411
|
-
data: dict[str, Any] = {"query": query, "num": num}
|
|
412
|
-
if symbols:
|
|
413
|
-
data["symbols"] = symbols
|
|
414
|
-
if start_datetime:
|
|
415
|
-
data["start_datetime"] = start_datetime
|
|
416
|
-
if end_datetime:
|
|
417
|
-
data["end_datetime"] = end_datetime
|
|
418
|
-
|
|
419
|
-
response = self._post("/v2/search/minutes", json=data)
|
|
420
|
-
return response.get("docs", [])
|
|
421
|
-
|
|
422
|
-
def search_socials(
|
|
423
|
-
self,
|
|
424
|
-
query: str,
|
|
425
|
-
*,
|
|
426
|
-
num: int = 10,
|
|
427
|
-
symbols: list[str] | None = None,
|
|
428
|
-
channel_ids: list[str] | None = None,
|
|
429
|
-
start_datetime: str | None = None,
|
|
430
|
-
end_datetime: str | None = None,
|
|
431
|
-
) -> list[dict[str, Any]]:
|
|
432
|
-
"""
|
|
433
|
-
Search social media content and market sentiment
|
|
434
|
-
|
|
435
|
-
Args:
|
|
436
|
-
query: Search query string
|
|
437
|
-
num: Number of results to return
|
|
438
|
-
symbols: Filter by stock symbols
|
|
439
|
-
channel_ids: Filter by channel IDs
|
|
440
|
-
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
441
|
-
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
442
|
-
|
|
443
|
-
Returns:
|
|
444
|
-
List of social media content
|
|
445
|
-
"""
|
|
446
|
-
data: dict[str, Any] = {"query": query, "num": num}
|
|
447
|
-
if symbols:
|
|
448
|
-
data["symbols"] = symbols
|
|
449
|
-
if channel_ids:
|
|
450
|
-
data["channel_ids"] = channel_ids
|
|
451
|
-
if start_datetime:
|
|
452
|
-
data["start_datetime"] = start_datetime
|
|
453
|
-
if end_datetime:
|
|
454
|
-
data["end_datetime"] = end_datetime
|
|
455
|
-
|
|
456
|
-
response = self._post("/v2/search/socials", json=data)
|
|
457
|
-
return response.get("docs", [])
|
|
458
|
-
|
|
459
|
-
def search_webpages(
|
|
460
|
-
self,
|
|
461
|
-
query: str,
|
|
462
|
-
*,
|
|
463
|
-
num: int = 10,
|
|
464
|
-
start_datetime: str | None = None,
|
|
465
|
-
end_datetime: str | None = None,
|
|
466
|
-
) -> list[dict[str, Any]]:
|
|
467
|
-
"""
|
|
468
|
-
Search webpage content
|
|
469
|
-
|
|
470
|
-
Args:
|
|
471
|
-
query: Search query string
|
|
472
|
-
num: Number of results to return
|
|
473
|
-
start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
474
|
-
end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
|
|
475
|
-
|
|
476
|
-
Returns:
|
|
477
|
-
List of webpage content
|
|
478
|
-
"""
|
|
479
|
-
data: dict[str, Any] = {"query": query, "num": num}
|
|
480
|
-
if start_datetime:
|
|
481
|
-
data["start_datetime"] = start_datetime
|
|
482
|
-
if end_datetime:
|
|
483
|
-
data["end_datetime"] = end_datetime
|
|
484
|
-
|
|
485
|
-
response = self._post("/v2/search/webpages", json=data)
|
|
486
|
-
return response.get("docs", [])
|
|
487
|
-
|
|
488
|
-
# -------------------------------------------------------------------------
|
|
489
|
-
# Sub-modules (lazy loading)
|
|
490
|
-
# -------------------------------------------------------------------------
|
|
491
|
-
|
|
492
|
-
@property
|
|
493
|
-
def stock(self):
|
|
494
|
-
"""Stock data module for financial statements, prices, etc."""
|
|
495
|
-
if self._stock is None:
|
|
496
|
-
from reportify_sdk.stock import StockModule
|
|
497
|
-
self._stock = StockModule(self)
|
|
498
|
-
return self._stock
|
|
499
|
-
|
|
500
|
-
@property
|
|
501
|
-
def timeline(self):
|
|
502
|
-
"""Timeline module for following companies, topics, etc."""
|
|
503
|
-
if self._timeline is None:
|
|
504
|
-
from reportify_sdk.timeline import TimelineModule
|
|
505
|
-
self._timeline = TimelineModule(self)
|
|
506
|
-
return self._timeline
|
|
507
|
-
|
|
508
|
-
@property
|
|
509
|
-
def kb(self):
|
|
510
|
-
"""Knowledge base module for searching user uploaded documents"""
|
|
511
|
-
if self._kb is None:
|
|
512
|
-
from reportify_sdk.kb import KBModule
|
|
513
|
-
self._kb = KBModule(self)
|
|
514
|
-
return self._kb
|
|
515
|
-
|
|
516
|
-
@property
|
|
517
|
-
def docs(self):
|
|
518
|
-
"""Documents module for accessing document content and metadata"""
|
|
519
|
-
if self._docs is None:
|
|
520
|
-
from reportify_sdk.docs import DocsModule
|
|
521
|
-
self._docs = DocsModule(self)
|
|
522
|
-
return self._docs
|
|
523
|
-
|
|
524
|
-
@property
|
|
525
|
-
def quant(self):
|
|
526
|
-
"""Quant module for indicators, factors, quotes, and backtesting"""
|
|
527
|
-
if self._quant is None:
|
|
528
|
-
from reportify_sdk.quant import QuantModule
|
|
529
|
-
self._quant = QuantModule(self)
|
|
530
|
-
return self._quant
|
|
531
|
-
|
|
532
|
-
@property
|
|
533
|
-
def concepts(self):
|
|
534
|
-
"""Concepts module for accessing concept data and feeds"""
|
|
535
|
-
if self._concepts is None:
|
|
536
|
-
from reportify_sdk.concepts import ConceptsModule
|
|
537
|
-
self._concepts = ConceptsModule(self)
|
|
538
|
-
return self._concepts
|
|
539
|
-
|
|
540
|
-
@property
|
|
541
|
-
def channels(self):
|
|
542
|
-
"""Channels module for searching and following channels"""
|
|
543
|
-
if self._channels is None:
|
|
544
|
-
from reportify_sdk.channels import ChannelsModule
|
|
545
|
-
self._channels = ChannelsModule(self)
|
|
546
|
-
return self._channels
|
|
547
|
-
|
|
548
|
-
@property
|
|
549
|
-
def chat(self):
|
|
550
|
-
"""Chat module for document-based Q&A"""
|
|
551
|
-
if self._chat is None:
|
|
552
|
-
from reportify_sdk.chat import ChatModule
|
|
553
|
-
self._chat = ChatModule(self)
|
|
554
|
-
return self._chat
|
|
555
|
-
|
|
556
|
-
@property
|
|
557
|
-
def agent(self):
|
|
558
|
-
"""Agent module for AI-powered conversations and workflows"""
|
|
559
|
-
if self._agent is None:
|
|
560
|
-
from reportify_sdk.agent import AgentModule
|
|
561
|
-
self._agent = AgentModule(self)
|
|
562
|
-
return self._agent
|
|
563
|
-
|
|
564
|
-
@property
|
|
565
|
-
def user(self):
|
|
566
|
-
"""User module for user-related tools and data"""
|
|
567
|
-
if self._user is None:
|
|
568
|
-
from reportify_sdk.user import UserModule
|
|
569
|
-
self._user = UserModule(self)
|
|
570
|
-
return self._user
|
|
571
|
-
|
|
572
|
-
def close(self):
|
|
573
|
-
"""Close the HTTP client"""
|
|
574
|
-
self._client.close()
|
|
575
|
-
|
|
576
|
-
def __enter__(self):
|
|
577
|
-
return self
|
|
578
|
-
|
|
579
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
580
|
-
self.close()
|
|
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
|