sovai 0.2.35__tar.gz → 0.2.36__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.
- {sovai-0.2.35 → sovai-0.2.36}/PKG-INFO +1 -1
- {sovai-0.2.35 → sovai-0.2.36}/pyproject.toml +1 -1
- {sovai-0.2.35 → sovai-0.2.36}/sovai/get_data.py +30 -20
- {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/authentication/authentication.py +3 -3
- sovai-0.2.36/sovai/utils/client_side_s3_part_high.py +321 -0
- sovai-0.2.35/sovai/utils/client_side_s3 copy.py +0 -206
- {sovai-0.2.35 → sovai-0.2.36}/LICENSE +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/README.md +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/.DS_Store +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/api_config.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/assets/dropdown.css +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/assets/features_mapping.parq +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/basic_auth.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/computations/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/computations/functions.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/errors/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/errors/sovai_errors.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/anomalies.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/ask_df_llm.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/change_point_generator.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/clustering.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/core_kshape.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/cum_sum_torch.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/cusum.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/dimensionality_reduction.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/feature_extraction.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/feature_importance.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/feature_neutralizer.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/filter_df.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/fractional_differencing.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/nowcasting.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/pairwise.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/pandas_extensions.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/parallel_functions.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/pfa_feature_selector.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/regime_change.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/regime_change_pca.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/shapley_global_importance.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/shapley_importance.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/signal_evaluation.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/technical_indicators.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/time_decomposition.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/weight_optimization.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/get_compute.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/get_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/get_reports.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/get_tools.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/accounting/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/accounting/accounting_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/allocation/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/allocation/allocation_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/bankruptcy/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/bankruptcy/bankruptcy_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/breakout/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/breakout/breakout_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/corp_risk/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/corp_risk/corp_risk_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/earnings_surprise/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/earnings_surprise/earnings_surprise_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/insider/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/insider/insider_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/institutional/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/institutional/institutional_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/news/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/news/news_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/ratios/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/ratios/ratios_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/accounting/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/accounting/accounting_balance_sheet.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/bankruptcy/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/bankruptcy/bankruptcy_monthly_top.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/general/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/general/general_plots.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/news/__inti__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/news/news_econometric_analysis.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/studies/nowcasting.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/token_auth.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/authentication/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/sec/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/sec/graphs.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/sec/llm_code_generator.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/sec/sec_10_k_8_k_filings.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/sec/sec_edgar_search.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/__init__.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/client_side.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/client_side_s3.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/converter.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/datetime_formats.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/file_management.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/get_tickers.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/helpers.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/pandas_extensions copy.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/plot.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/port_manager.py +0 -0
- {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/stream.py +0 -0
|
@@ -20,8 +20,11 @@ from sovai.errors.sovai_errors import InvalidInputData
|
|
|
20
20
|
from sovai.utils.converter import convert_data2df
|
|
21
21
|
from sovai.utils.stream import stream_data, stream_data_pyarrow
|
|
22
22
|
from sovai.utils.datetime_formats import datetime_format
|
|
23
|
+
|
|
23
24
|
from sovai.utils.client_side import client_side_frame
|
|
24
25
|
from sovai.utils.client_side_s3 import load_frame_s3
|
|
26
|
+
from sovai.utils.client_side_s3_part_high import load_frame_s3_paritioned_high
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
## Note run these for TEST - IT COULD BE HERE some silent problems
|
|
27
30
|
try:
|
|
@@ -296,6 +299,9 @@ def normalize_endpoint(endpoint):
|
|
|
296
299
|
|
|
297
300
|
## If you also want access by full file, please also add to endpoint_to_ticker
|
|
298
301
|
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# Define your endpoint sets
|
|
299
305
|
client_side_endpoints_gcs = {
|
|
300
306
|
"ratios/relative",
|
|
301
307
|
"market/prices",
|
|
@@ -306,7 +312,6 @@ client_side_endpoints_gcs = {
|
|
|
306
312
|
"lobbying/public",
|
|
307
313
|
}
|
|
308
314
|
|
|
309
|
-
## If you also want access by full file, please also add to endpoint_to_ticker
|
|
310
315
|
client_side_endpoints_s3 = {
|
|
311
316
|
"sec/10k",
|
|
312
317
|
"trials/predict",
|
|
@@ -314,9 +319,18 @@ client_side_endpoints_s3 = {
|
|
|
314
319
|
"trials/all/predict",
|
|
315
320
|
"trials/all/decribe",
|
|
316
321
|
"trials/all",
|
|
322
|
+
}
|
|
317
323
|
|
|
324
|
+
client_side_endpoints_s3_part_high = {
|
|
325
|
+
"patents/applications"
|
|
318
326
|
}
|
|
319
327
|
|
|
328
|
+
# Create a list of handlers
|
|
329
|
+
handlers = [
|
|
330
|
+
(client_side_endpoints_gcs, client_side_frame, "Grabbing GCS client side"),
|
|
331
|
+
(client_side_endpoints_s3, load_frame_s3, "Grabbing S3 client side"),
|
|
332
|
+
(client_side_endpoints_s3_part_high, load_frame_s3_paritioned_high, "Grabbing S3 Partitioned High client side"),
|
|
333
|
+
]
|
|
320
334
|
|
|
321
335
|
|
|
322
336
|
# In your main function or wherever this logic is implemented:
|
|
@@ -604,26 +618,22 @@ def data(
|
|
|
604
618
|
|
|
605
619
|
normalized_endpoint = normalize_endpoint(endpoint)
|
|
606
620
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
)
|
|
616
|
-
|
|
621
|
+
# ACTAULLY I DON'T CARE, TICKERS CAN BE NONE FOR PATENTS
|
|
622
|
+
|
|
623
|
+
# Iterate through the handlers to find a matching handler
|
|
624
|
+
for endpoint_set, handler_func, message in handlers:
|
|
625
|
+
if (
|
|
626
|
+
normalized_endpoint in endpoint_set and
|
|
627
|
+
(tickers is not None or start_date is not None) and
|
|
628
|
+
frequency is None
|
|
629
|
+
):
|
|
630
|
+
verbose_mode.vprint(message)
|
|
631
|
+
_query_cache[cache_key] = handler_func(
|
|
632
|
+
normalized_endpoint, tickers, columns, start_date, end_date
|
|
633
|
+
)
|
|
634
|
+
return _query_cache[cache_key]
|
|
635
|
+
|
|
617
636
|
|
|
618
|
-
elif (
|
|
619
|
-
normalized_endpoint in client_side_endpoints_s3
|
|
620
|
-
and tickers is not None
|
|
621
|
-
and frequency is None
|
|
622
|
-
):
|
|
623
|
-
verbose_mode.vprint("Grabbing s3 client side")
|
|
624
|
-
_query_cache[cache_key] = load_frame_s3(
|
|
625
|
-
normalized_endpoint, tickers, columns, start_date, end_date)
|
|
626
|
-
return _query_cache[cache_key]
|
|
627
637
|
try:
|
|
628
638
|
if tickers is not None and not is_all(tickers):
|
|
629
639
|
params = ticker_mapper(params,verbose)
|
|
@@ -7,7 +7,7 @@ from pyarrow.fs import S3FileSystem as S3File
|
|
|
7
7
|
import s3fs
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def get_s3_filesystem_pickle(storage_provider: str, verbose: bool = False):
|
|
11
11
|
"""
|
|
12
12
|
Retrieve the S3FileSystem for the specified storage provider using pickle.
|
|
13
13
|
|
|
@@ -70,8 +70,8 @@ def get_s3fs_filesystem_json(storage_provider: str, verbose: bool = False):
|
|
|
70
70
|
print(f"Successfully retrieved S3 credentials for {storage_provider}")
|
|
71
71
|
|
|
72
72
|
s3fsa = s3fs.S3FileSystem(
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
key=cred['access_key'],
|
|
74
|
+
secret=cred['secret_key'],
|
|
75
75
|
client_kwargs={'endpoint_url': cred['endpoint']}
|
|
76
76
|
)
|
|
77
77
|
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import pyarrow.dataset as ds
|
|
2
|
+
from pyarrow.fs import S3FileSystem
|
|
3
|
+
import pyarrow as pa
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
7
|
+
from sovai.tools.authentication import authentication
|
|
8
|
+
import os
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import pyarrow as pa
|
|
11
|
+
import pyarrow.dataset as ds
|
|
12
|
+
import s3fs
|
|
13
|
+
import logging
|
|
14
|
+
import datetime
|
|
15
|
+
from functools import lru_cache
|
|
16
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
17
|
+
from tqdm import tqdm
|
|
18
|
+
from pyarrow.fs import S3FileSystem
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Try to import CustomDataFrame, use regular DataFrame if not available
|
|
22
|
+
try:
|
|
23
|
+
from sovai.extensions.pandas_extensions import CustomDataFrame
|
|
24
|
+
HAS_CUSTOM_DATAFRAME = True
|
|
25
|
+
except ImportError:
|
|
26
|
+
HAS_CUSTOM_DATAFRAME = False
|
|
27
|
+
CustomDataFrame = pd.DataFrame # Fallback to regular DataFrame
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@lru_cache(maxsize=2)
|
|
31
|
+
def get_cached_s3_filesystem(storage_provider):
|
|
32
|
+
return authentication.get_s3_filesystem_pickle(storage_provider, verbose=True)
|
|
33
|
+
|
|
34
|
+
@lru_cache(maxsize=2)
|
|
35
|
+
def get_cached_s3fs_filesystem(storage_provider):
|
|
36
|
+
return authentication.get_s3fs_filesystem_json(storage_provider, verbose=True)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Configure logging to output to both console and a log file
|
|
40
|
+
logging.basicConfig(
|
|
41
|
+
level=logging.INFO,
|
|
42
|
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
43
|
+
handlers=[
|
|
44
|
+
logging.FileHandler("data_operations.log"),
|
|
45
|
+
logging.StreamHandler()
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
logger = logging.getLogger(__name__)
|
|
49
|
+
|
|
50
|
+
# -------------------------------
|
|
51
|
+
# Helper Functions
|
|
52
|
+
# -------------------------------
|
|
53
|
+
|
|
54
|
+
def construct_s3_path(type_name, provider, partition_type, ticker=None, publish_date=None, year=None, all_dates_after=False):
|
|
55
|
+
"""
|
|
56
|
+
Construct the S3 path based on the partitioning scheme.
|
|
57
|
+
|
|
58
|
+
Parameters:
|
|
59
|
+
- type_name (str): Type of data (e.g., 'applications')
|
|
60
|
+
- provider (str): 'wasabi' or 'digitalocean'
|
|
61
|
+
- partition_type (str): 'date' or 'ticker'
|
|
62
|
+
- ticker (str, optional): Ticker symbol
|
|
63
|
+
- publish_date (str, optional): Publish date in 'YYYY-MM-DD' format
|
|
64
|
+
- year (int, optional): Year extracted from publish_date
|
|
65
|
+
- all_dates_after (bool, optional): If True, scan all date partitions after a certain date
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
- str: Constructed S3 path
|
|
69
|
+
"""
|
|
70
|
+
if provider=="digitalocean":
|
|
71
|
+
bucket ="sovai/sovai-patents-export"
|
|
72
|
+
else:
|
|
73
|
+
bucket = "sovai-patents-export"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if partition_type == "date":
|
|
77
|
+
if all_dates_after:
|
|
78
|
+
# Return the parent directory to scan all date partitions
|
|
79
|
+
path = f"{type_name}/date/"
|
|
80
|
+
else:
|
|
81
|
+
if not publish_date:
|
|
82
|
+
raise ValueError("publish_date must be provided for date partitioning.")
|
|
83
|
+
path = f"{type_name}/date/date_partitioned={publish_date}/"
|
|
84
|
+
elif partition_type == "ticker":
|
|
85
|
+
if not ticker or not year:
|
|
86
|
+
raise ValueError("Both ticker and year must be provided for ticker partitioning.")
|
|
87
|
+
path = f"{type_name}/ticker/ticker_partitioned={ticker}/year={year}/"
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError("Invalid partition_type. Choose 'date' or 'ticker'.")
|
|
90
|
+
|
|
91
|
+
return f"{bucket}/{path}"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def list_date_partitions(provider, type_name):
|
|
95
|
+
s3_path = construct_s3_path(type_name, provider, partition_type="date", all_dates_after=True)
|
|
96
|
+
fs = get_cached_s3fs_filesystem(provider)
|
|
97
|
+
try:
|
|
98
|
+
partitions = fs.ls(s3_path)
|
|
99
|
+
dates = []
|
|
100
|
+
for partition in partitions:
|
|
101
|
+
basename = os.path.basename(partition.rstrip('/'))
|
|
102
|
+
if basename.startswith('date_partitioned='):
|
|
103
|
+
date_str = basename.split('=')[1]
|
|
104
|
+
try:
|
|
105
|
+
date_obj = datetime.datetime.strptime(date_str, '%Y-%m-%d').date()
|
|
106
|
+
dates.append((date_obj, partition))
|
|
107
|
+
except ValueError:
|
|
108
|
+
logger.warning(f"Unable to parse date from partition {partition}")
|
|
109
|
+
return dates
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.error(f"Error listing date partitions in {s3_path} ({provider}): {e}")
|
|
112
|
+
return []
|
|
113
|
+
|
|
114
|
+
def load_parquet_from_s3(s3_path, provider, columns=None, start_date=None):
|
|
115
|
+
"""
|
|
116
|
+
Load a Parquet file from S3 into a pandas DataFrame with optional date filtering.
|
|
117
|
+
|
|
118
|
+
Parameters:
|
|
119
|
+
- s3_path (str): S3 path to the Parquet file or directory
|
|
120
|
+
- provider (str): 'wasabi' or 'digitalocean'
|
|
121
|
+
- columns (list, optional): List of columns to select
|
|
122
|
+
- start_date (str, optional): Minimum date in 'YYYY-MM-DD' format to filter the data
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
- pandas.DataFrame: Loaded DataFrame
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
fs = get_cached_s3_filesystem(provider)
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
dataset = ds.dataset(s3_path, filesystem=fs, format='parquet')
|
|
132
|
+
|
|
133
|
+
if start_date:
|
|
134
|
+
# Convert start_date string to pyarrow date32 scalar
|
|
135
|
+
min_date_pa = pa.scalar(pd.to_datetime(start_date).date(), type=pa.date32())
|
|
136
|
+
# Use ds.field to construct the filter expression
|
|
137
|
+
filter_expr = ds.field('date') >= min_date_pa
|
|
138
|
+
dataset = dataset.filter(filter_expr)
|
|
139
|
+
|
|
140
|
+
table = dataset.to_table(columns=columns)
|
|
141
|
+
|
|
142
|
+
# Log the schema for debugging
|
|
143
|
+
logger.info(f"Schema for {s3_path} ({provider}):")
|
|
144
|
+
logger.info(table.schema)
|
|
145
|
+
|
|
146
|
+
df = table.to_pandas()
|
|
147
|
+
|
|
148
|
+
partition_cols = ['ticker', 'date']
|
|
149
|
+
existing_cols = [col for col in partition_cols if col in df.columns]
|
|
150
|
+
other_cols = [col for col in df.columns if col not in partition_cols]
|
|
151
|
+
df = df[existing_cols + other_cols]
|
|
152
|
+
|
|
153
|
+
return df
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.error(f"Error loading data from {s3_path} ({provider}): {e}")
|
|
156
|
+
return pd.DataFrame()
|
|
157
|
+
|
|
158
|
+
# -------------------------------
|
|
159
|
+
# Loading Functions
|
|
160
|
+
# -------------------------------
|
|
161
|
+
|
|
162
|
+
def load_data_by_ticker(
|
|
163
|
+
type_name,
|
|
164
|
+
providers=['wasabi', 'digitalocean'],
|
|
165
|
+
tickers=None,
|
|
166
|
+
start_date=None,
|
|
167
|
+
columns=None,
|
|
168
|
+
max_workers=4
|
|
169
|
+
):
|
|
170
|
+
"""
|
|
171
|
+
Load data for specified tickers and/or dates from S3 storage providers.
|
|
172
|
+
|
|
173
|
+
Parameters:
|
|
174
|
+
- type_name (str): Type of data (e.g., 'applications', 'prediction_all', etc.)
|
|
175
|
+
- providers (list, optional): List of storage providers to load from ('wasabi', 'digitalocean')
|
|
176
|
+
- tickers (str or list, optional): Ticker symbol(s) to load data for
|
|
177
|
+
- start_date (str, optional): Minimum publishDate to filter data by (format 'YYYY-MM-DD') (only for date partitioning)
|
|
178
|
+
- columns (list, optional): List of columns to select
|
|
179
|
+
- max_workers (int, optional): Number of parallel threads
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
- pandas.DataFrame: Concatenated DataFrame containing the loaded data
|
|
183
|
+
"""
|
|
184
|
+
date_tasks = []
|
|
185
|
+
ticker_tasks = []
|
|
186
|
+
results = []
|
|
187
|
+
|
|
188
|
+
# Normalize input parameters
|
|
189
|
+
if tickers and isinstance(tickers, str):
|
|
190
|
+
tickers = [tickers]
|
|
191
|
+
if not tickers:
|
|
192
|
+
tickers = []
|
|
193
|
+
|
|
194
|
+
# If both tickers and min_date are provided
|
|
195
|
+
if tickers and start_date:
|
|
196
|
+
try:
|
|
197
|
+
min_date_obj = datetime.datetime.strptime(start_date, '%Y-%m-%d').date()
|
|
198
|
+
min_year = min_date_obj.year
|
|
199
|
+
logger.info(f"Minimum date provided: {min_date_obj} (Year: {min_year})")
|
|
200
|
+
except ValueError:
|
|
201
|
+
logger.error(f"Invalid min_date format: {start_date}. Expected 'YYYY-MM-DD'.")
|
|
202
|
+
return pd.DataFrame()
|
|
203
|
+
|
|
204
|
+
for provider in providers:
|
|
205
|
+
for ticker in tickers:
|
|
206
|
+
# Determine the range of years to load based on start_date
|
|
207
|
+
# Assuming data starts from 2011
|
|
208
|
+
start_year = min_year
|
|
209
|
+
current_year = datetime.datetime.now().year
|
|
210
|
+
for year in range(start_year, current_year + 1):
|
|
211
|
+
s3_path = construct_s3_path(
|
|
212
|
+
type_name, provider, partition_type="ticker",
|
|
213
|
+
ticker=ticker, year=year
|
|
214
|
+
)
|
|
215
|
+
ticker_tasks.append((s3_path, provider, columns, None)) # No additional filtering here
|
|
216
|
+
|
|
217
|
+
# If only min_date is provided
|
|
218
|
+
elif start_date and not tickers:
|
|
219
|
+
try:
|
|
220
|
+
min_date_obj = datetime.datetime.strptime(start_date, '%Y-%m-%d').date()
|
|
221
|
+
logger.info(f"Minimum date provided: {min_date_obj}")
|
|
222
|
+
except ValueError:
|
|
223
|
+
logger.error(f"Invalid start_date format: {start_date}. Expected 'YYYY-MM-DD'.")
|
|
224
|
+
return pd.DataFrame()
|
|
225
|
+
|
|
226
|
+
for provider in providers:
|
|
227
|
+
# List all date-based partitions
|
|
228
|
+
date_partitions = list_date_partitions(provider, type_name)
|
|
229
|
+
for date_obj, partition_path in date_partitions:
|
|
230
|
+
if date_obj >= min_date_obj:
|
|
231
|
+
date_tasks.append((partition_path, provider, columns, None))
|
|
232
|
+
|
|
233
|
+
# If only tickers are provided
|
|
234
|
+
elif tickers and not start_date:
|
|
235
|
+
for provider in providers:
|
|
236
|
+
for ticker in tickers:
|
|
237
|
+
# Assuming data starts from 2011
|
|
238
|
+
start_year = 2010 ## Change this to 2008 eventually
|
|
239
|
+
current_year = datetime.datetime.now().year
|
|
240
|
+
for year in range(start_year, current_year + 1):
|
|
241
|
+
s3_path = construct_s3_path(
|
|
242
|
+
type_name, provider, partition_type="ticker",
|
|
243
|
+
ticker=ticker, year=year
|
|
244
|
+
)
|
|
245
|
+
ticker_tasks.append((s3_path, provider, columns, None))
|
|
246
|
+
|
|
247
|
+
else:
|
|
248
|
+
logger.warning("No tickers or start_date provided. Please provide at least one.")
|
|
249
|
+
return pd.DataFrame()
|
|
250
|
+
|
|
251
|
+
# Combine all tasks
|
|
252
|
+
all_tasks = ticker_tasks + date_tasks
|
|
253
|
+
|
|
254
|
+
if not all_tasks:
|
|
255
|
+
logger.warning("No tasks to process. Exiting.")
|
|
256
|
+
return pd.DataFrame()
|
|
257
|
+
|
|
258
|
+
def load_task(task):
|
|
259
|
+
s3_path, provider, columns, ticker_filter = task
|
|
260
|
+
df = load_parquet_from_s3(s3_path, provider, columns, start_date=start_date if ticker_filter is None else None)
|
|
261
|
+
if not df.empty and ticker_filter:
|
|
262
|
+
if 'ticker' in df.columns:
|
|
263
|
+
df = df[df['ticker'].isin(ticker_filter)]
|
|
264
|
+
else:
|
|
265
|
+
logger.warning(f"'ticker' column not found in data from {s3_path} ({provider}). Skipping ticker filter.")
|
|
266
|
+
return df
|
|
267
|
+
|
|
268
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
269
|
+
future_to_task = {executor.submit(load_task, task): task for task in all_tasks}
|
|
270
|
+
for future in tqdm(as_completed(future_to_task), total=len(all_tasks), desc="Loading data"):
|
|
271
|
+
df = future.result()
|
|
272
|
+
if not df.empty:
|
|
273
|
+
results.append(df)
|
|
274
|
+
|
|
275
|
+
if results:
|
|
276
|
+
final_df = pd.concat(results, ignore_index=True)
|
|
277
|
+
logger.info(f"Successfully loaded data with {len(final_df)} records.")
|
|
278
|
+
else:
|
|
279
|
+
final_df = pd.DataFrame()
|
|
280
|
+
logger.warning("No data found.")
|
|
281
|
+
|
|
282
|
+
return final_df
|
|
283
|
+
|
|
284
|
+
# -------------------------------
|
|
285
|
+
# Example Usage
|
|
286
|
+
# -------------------------------
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def load_frame_s3_paritioned_high(endpoint, tickers=None, columns=None, start_date=None, end_date=None):
|
|
290
|
+
|
|
291
|
+
endpoint_config = {
|
|
292
|
+
"patents/applications": {
|
|
293
|
+
"bucket": {
|
|
294
|
+
"digitalocean": "sovai/sovai-patents-export",
|
|
295
|
+
"wasabi": "sovai-patents-export"
|
|
296
|
+
},
|
|
297
|
+
"type": "applications"
|
|
298
|
+
},
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if endpoint not in endpoint_config:
|
|
302
|
+
raise ValueError(f"Invalid endpoint: {endpoint}")
|
|
303
|
+
|
|
304
|
+
config = endpoint_config[endpoint]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
loaded_df_both = load_data_by_ticker(
|
|
308
|
+
type_name=config["type"],
|
|
309
|
+
providers=['digitalocean'],
|
|
310
|
+
tickers=tickers,
|
|
311
|
+
start_date=start_date,
|
|
312
|
+
columns=columns,
|
|
313
|
+
max_workers=8 # Adjust based on your system's capabilities
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
if HAS_CUSTOM_DATAFRAME:
|
|
318
|
+
return CustomDataFrame(loaded_df_both)
|
|
319
|
+
else:
|
|
320
|
+
return loaded_df_both # Returns a regular pandas DataFrame if CustomDataFrame is not available
|
|
321
|
+
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import pyarrow.dataset as ds
|
|
2
|
-
from pyarrow.fs import S3FileSystem
|
|
3
|
-
import pyarrow as pa
|
|
4
|
-
import pandas as pd
|
|
5
|
-
from functools import lru_cache
|
|
6
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
7
|
-
from sovai.tools.authentication import authentication
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# Try to import CustomDataFrame, use regular DataFrame if not available
|
|
11
|
-
try:
|
|
12
|
-
from sovai.extensions.pandas_extensions import CustomDataFrame
|
|
13
|
-
HAS_CUSTOM_DATAFRAME = True
|
|
14
|
-
except ImportError:
|
|
15
|
-
HAS_CUSTOM_DATAFRAME = False
|
|
16
|
-
CustomDataFrame = pd.DataFrame # Fallback to regular DataFrame
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@lru_cache(maxsize=2)
|
|
20
|
-
def get_cached_s3_filesystem(storage_provider):
|
|
21
|
-
return authentication.get_s3_filesystem_pickle(storage_provider, verbose=True)
|
|
22
|
-
|
|
23
|
-
def process_partition(storage_provider, base_path, identifier_column, identifier=None, columns=None, filters=None):
|
|
24
|
-
s3 = get_cached_s3_filesystem(storage_provider)
|
|
25
|
-
|
|
26
|
-
if identifier:
|
|
27
|
-
base_path += f"/{identifier_column}={identifier}"
|
|
28
|
-
|
|
29
|
-
dataset = ds.dataset(base_path, filesystem=s3, format='parquet')
|
|
30
|
-
|
|
31
|
-
if filters:
|
|
32
|
-
operator_map = {
|
|
33
|
-
'==': lambda a, b: a == b,
|
|
34
|
-
'!=': lambda a, b: a != b,
|
|
35
|
-
'<': lambda a, b: a < b,
|
|
36
|
-
'<=': lambda a, b: a <= b,
|
|
37
|
-
'>': lambda a, b: a > b,
|
|
38
|
-
'>=': lambda a, b: a >= b,
|
|
39
|
-
'in': lambda a, b: a.isin(b),
|
|
40
|
-
'not in': lambda a, b: ~a.isin(b),
|
|
41
|
-
'like': lambda a, b: a.match_substring(b),
|
|
42
|
-
}
|
|
43
|
-
schema = dataset.schema
|
|
44
|
-
filter_expr = None
|
|
45
|
-
for col, op, val in filters:
|
|
46
|
-
if op not in operator_map:
|
|
47
|
-
raise ValueError(f"Unsupported operator '{op}' in filters.")
|
|
48
|
-
field = ds.field(col)
|
|
49
|
-
field_type = schema.field(col).type
|
|
50
|
-
if pa.types.is_timestamp(field_type) or pa.types.is_date(field_type):
|
|
51
|
-
val = pd.to_datetime(val)
|
|
52
|
-
elif pa.types.is_integer(field_type):
|
|
53
|
-
val = int(val)
|
|
54
|
-
elif pa.types.is_floating(field_type):
|
|
55
|
-
val = float(val)
|
|
56
|
-
condition = operator_map[op](field, val)
|
|
57
|
-
filter_expr = condition if filter_expr is None else filter_expr & condition
|
|
58
|
-
else:
|
|
59
|
-
filter_expr = None
|
|
60
|
-
|
|
61
|
-
table = dataset.to_table(columns=columns, filter=filter_expr, use_threads=True)
|
|
62
|
-
df = table.to_pandas(use_threads=True)
|
|
63
|
-
|
|
64
|
-
if identifier:
|
|
65
|
-
df[identifier_column] = identifier
|
|
66
|
-
|
|
67
|
-
df = df[[identifier_column] + [col for col in df.columns if col != identifier_column]]
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return df
|
|
71
|
-
|
|
72
|
-
import numpy as np
|
|
73
|
-
|
|
74
|
-
def client_side_s3_frame(config, identifiers, columns, start_date, end_date):
|
|
75
|
-
storage_provider = "digitalocean" # Default to DigitalOcean
|
|
76
|
-
base_path = config["storage_provider"][storage_provider]
|
|
77
|
-
identifier_column = config.get("identifier_column")
|
|
78
|
-
|
|
79
|
-
filters = []
|
|
80
|
-
if start_date:
|
|
81
|
-
filters.append(('date', '>=', start_date))
|
|
82
|
-
if end_date:
|
|
83
|
-
filters.append(('date', '<=', end_date))
|
|
84
|
-
|
|
85
|
-
if not identifiers:
|
|
86
|
-
# Load the entire database
|
|
87
|
-
return process_partition(storage_provider, base_path, identifier_column, columns=columns, filters=filters)
|
|
88
|
-
else:
|
|
89
|
-
identifiers = map_identifier(identifiers, input_identifier='ticker', output_identifier=identifier_column)
|
|
90
|
-
|
|
91
|
-
# Use numpy.isscalar to check if identifiers is a single value
|
|
92
|
-
if np.isscalar(identifiers):
|
|
93
|
-
# Single identifier
|
|
94
|
-
identifier = identifiers
|
|
95
|
-
return process_partition(storage_provider, base_path, identifier_column, identifier=identifier, columns=columns, filters=filters)
|
|
96
|
-
|
|
97
|
-
# Multiple identifiers
|
|
98
|
-
with ThreadPoolExecutor() as executor:
|
|
99
|
-
futures = []
|
|
100
|
-
for identifier in identifiers:
|
|
101
|
-
futures.append(executor.submit(process_partition, storage_provider, base_path, identifier_column, identifier, columns, filters))
|
|
102
|
-
|
|
103
|
-
results = []
|
|
104
|
-
for future in as_completed(futures):
|
|
105
|
-
results.append(future.result())
|
|
106
|
-
|
|
107
|
-
df = pd.concat(results, ignore_index=True)
|
|
108
|
-
return df
|
|
109
|
-
|
|
110
|
-
# if 'filing_date' in df.columns:
|
|
111
|
-
# df['date'] = pd.to_datetime(df['filing_date'])
|
|
112
|
-
# elif 'date' not in df.columns:
|
|
113
|
-
# raise ValueError("Neither 'filing_date' nor 'date' column found in the data")
|
|
114
|
-
|
|
115
|
-
# df.set_index([identifier_column, 'date'], inplace=True)
|
|
116
|
-
# df.sort_index(inplace=True)
|
|
117
|
-
|
|
118
|
-
return df
|
|
119
|
-
|
|
120
|
-
import pandas as pd
|
|
121
|
-
import numpy as np
|
|
122
|
-
|
|
123
|
-
def map_identifier(input_values, input_identifier='ticker', output_identifier='cik', trailing_zero_removal=True):
|
|
124
|
-
"""
|
|
125
|
-
Map from one identifier to another using the df_codes DataFrame.
|
|
126
|
-
Handles string, single-item list, and multiple-item lists as input.
|
|
127
|
-
Uses vectorization for improved performance.
|
|
128
|
-
|
|
129
|
-
:param input_values: The input identifier value(s) to map from (string, list, or array-like)
|
|
130
|
-
:param input_identifier: The type of input identifier (default: 'ticker')
|
|
131
|
-
:param output_identifier: The type of output identifier (default: 'cik')
|
|
132
|
-
:param trailing_zero_removal: If True, convert CIK to int (default: True)
|
|
133
|
-
:return: Mapped identifier value(s), same type as input
|
|
134
|
-
"""
|
|
135
|
-
df_codes = pd.read_parquet("data/codes.parq")
|
|
136
|
-
|
|
137
|
-
# Check if the identifier columns exist in df_codes
|
|
138
|
-
if input_identifier not in df_codes.columns or output_identifier not in df_codes.columns:
|
|
139
|
-
raise ValueError(f"Identifier '{input_identifier}' or '{output_identifier}' not found in the DataFrame")
|
|
140
|
-
|
|
141
|
-
# Convert input to numpy array for consistent handling
|
|
142
|
-
if isinstance(input_values, str):
|
|
143
|
-
input_array = np.array([input_values])
|
|
144
|
-
input_is_string = True
|
|
145
|
-
else:
|
|
146
|
-
input_array = np.array(input_values)
|
|
147
|
-
input_is_string = False
|
|
148
|
-
|
|
149
|
-
# Ensure all input values are strings
|
|
150
|
-
input_array = np.char.strip(input_array.astype(str))
|
|
151
|
-
|
|
152
|
-
# Create a dictionary for fast lookup
|
|
153
|
-
mapping_dict = dict(zip(df_codes[input_identifier], df_codes[output_identifier]))
|
|
154
|
-
|
|
155
|
-
# Vectorized mapping
|
|
156
|
-
result = np.vectorize(mapping_dict.get)(input_array)
|
|
157
|
-
|
|
158
|
-
# Convert to int if trailing_zero_removal is True and output is CIK
|
|
159
|
-
if trailing_zero_removal and output_identifier.lower() == 'cik':
|
|
160
|
-
result = np.where(result == None, 0, result) # Replace None with 0
|
|
161
|
-
result = result.astype(np.int32) # Convert to int64
|
|
162
|
-
|
|
163
|
-
# Return result in the same format as input
|
|
164
|
-
if input_is_string:
|
|
165
|
-
return result[0]
|
|
166
|
-
elif len(input_array) == 1:
|
|
167
|
-
return result[0]
|
|
168
|
-
else:
|
|
169
|
-
return result.tolist()
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def load_frame_s3(endpoint, tickers=None, columns=None, start_date=None, end_date=None):
|
|
173
|
-
|
|
174
|
-
endpoint_config = {
|
|
175
|
-
|
|
176
|
-
"patents/applications": {
|
|
177
|
-
"storage_provider": {
|
|
178
|
-
"digitalocean": "sovai/sovai-patents-export/applications",
|
|
179
|
-
"wasabi": "sovai-patents-export/applications"
|
|
180
|
-
},
|
|
181
|
-
"identifier_columns": ["date_partitioned", "ticker_partitioned"],
|
|
182
|
-
"partitioned": True,
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if endpoint not in endpoint_config:
|
|
188
|
-
raise ValueError(f"Invalid endpoint: {endpoint}")
|
|
189
|
-
|
|
190
|
-
config = endpoint_config[endpoint]
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
df_frame = client_side_s3_frame(
|
|
194
|
-
config,
|
|
195
|
-
tickers,
|
|
196
|
-
columns,
|
|
197
|
-
start_date,
|
|
198
|
-
end_date
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if HAS_CUSTOM_DATAFRAME:
|
|
203
|
-
return CustomDataFrame(df_frame)
|
|
204
|
-
else:
|
|
205
|
-
return df_frame # Returns a regular pandas DataFrame if CustomDataFrame is not available
|
|
206
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|