bigdata-smart-batching 1.4.0__tar.gz → 1.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bigdata_smart_batching-1.4.0/README.md → bigdata_smart_batching-1.5.0/PKG-INFO +20 -1
- bigdata_smart_batching-1.4.0/PKG-INFO → bigdata_smart_batching-1.5.0/README.md +4 -17
- {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/examples/example.py +21 -19
- {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/pyproject.toml +9 -1
- {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/src/bigdata_smart_batching/__init__.py +2 -0
- {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/src/bigdata_smart_batching/output_converter.py +3 -1
- {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/src/bigdata_smart_batching/search_function.py +27 -13
- {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/src/bigdata_smart_batching/smart_batching.py +13 -19
- bigdata_smart_batching-1.4.0/CHANGELOG.md +0 -159
- bigdata_smart_batching-1.4.0/Makefile +0 -19
- bigdata_smart_batching-1.4.0/docs/smart-batching-architecture.md +0 -481
- bigdata_smart_batching-1.4.0/examples/example_non_determinism.py +0 -97
- bigdata_smart_batching-1.4.0/examples/example_overlapping.py +0 -185
- bigdata_smart_batching-1.4.0/tests/__init__.py +0 -0
- bigdata_smart_batching-1.4.0/tests/test_config.py +0 -30
- bigdata_smart_batching-1.4.0/tests/test_output_converter.py +0 -82
- bigdata_smart_batching-1.4.0/tests/test_rate_limiter.py +0 -120
- bigdata_smart_batching-1.4.0/tests/test_validation.py +0 -342
- bigdata_smart_batching-1.4.0/uv.lock +0 -1045
- {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/.gitignore +0 -0
- {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/LICENSE +0 -0
- {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/src/bigdata_smart_batching/smart_batching_config.py +0 -0
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: bigdata-smart-batching
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Summary: High-performance semantic search with intelligent company grouping and parallel execution
|
|
5
|
+
Project-URL: homepage, https://bigdata.com/api
|
|
6
|
+
Author-email: "Bigdata.com" <support@ravenpack.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: <4.0,>=3.11
|
|
10
|
+
Requires-Dist: matplotlib>=3.7
|
|
11
|
+
Requires-Dist: numpy>=1.24
|
|
12
|
+
Requires-Dist: pandas>=2.0
|
|
13
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
14
|
+
Requires-Dist: requests>=2.31.0
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
1
17
|
# Smart Batching Search
|
|
2
18
|
|
|
3
19
|
A high-performance semantic search system that reduces API queries by **67-99%** (varies by topic specificity) through intelligent company grouping and parallel execution.
|
|
@@ -267,6 +283,8 @@ Step 2: EXECUTION
|
|
|
267
283
|
3. Optionally performs a **second pass**, removing already-retrieved entities from each basket and re-querying for the remainder.
|
|
268
284
|
4. Enriches each chunk with `entity_ids` and `primary_entity_id`, and returns the raw documents.
|
|
269
285
|
|
|
286
|
+
Every search request is sent with `response_detail: "extended"`, so each document includes its filer in `reporting_entity` (a list of entity IDs). The filer is used as a chunk's `entity_ids` when the chunk has no detections, and the second pass counts it as an entity already retrieved.
|
|
287
|
+
|
|
270
288
|
## API Reference
|
|
271
289
|
|
|
272
290
|
### `plan_search()`
|
|
@@ -308,7 +326,8 @@ Step 2: EXECUTION
|
|
|
308
326
|
### Helper Functions
|
|
309
327
|
|
|
310
328
|
- `deduplicate_documents(documents)` -- merge duplicate documents by `id`, combining their chunks.
|
|
311
|
-
- `convert_to_dataframe(raw_results)` -- convert documents to a DataFrame with one row per chunk.
|
|
329
|
+
- `convert_to_dataframe(raw_results)` -- convert documents to a DataFrame with one row per chunk. The `reporting_entities` column holds the document's filer ids.
|
|
330
|
+
- `get_reporting_entities(document)` -- return the filer ids of a search document (`reporting_entity`; the legacy `reporting_entities` / `reportingEntities` keys are also accepted).
|
|
312
331
|
- `resolve_universe(universe)` -- load entity IDs from a CSV path or validate a `list[str]`.
|
|
313
332
|
- `load_universe_from_csv(csv_path)` -- load entity IDs from a CSV only.
|
|
314
333
|
- `save_plan(plan, path)` / `load_plan(path)` -- persist plans as JSON.
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: bigdata-smart-batching
|
|
3
|
-
Version: 1.4.0
|
|
4
|
-
Summary: High-performance semantic search with intelligent company grouping and parallel execution
|
|
5
|
-
Project-URL: homepage, https://bigdata.com/api
|
|
6
|
-
Author-email: "Bigdata.com" <support@ravenpack.com>
|
|
7
|
-
License: MIT
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Requires-Python: <4.0,>=3.11
|
|
10
|
-
Requires-Dist: matplotlib>=3.7
|
|
11
|
-
Requires-Dist: numpy>=1.24
|
|
12
|
-
Requires-Dist: pandas>=2.0
|
|
13
|
-
Requires-Dist: python-dotenv>=1.2.2
|
|
14
|
-
Requires-Dist: requests>=2.31.0
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
|
|
17
1
|
# Smart Batching Search
|
|
18
2
|
|
|
19
3
|
A high-performance semantic search system that reduces API queries by **67-99%** (varies by topic specificity) through intelligent company grouping and parallel execution.
|
|
@@ -283,6 +267,8 @@ Step 2: EXECUTION
|
|
|
283
267
|
3. Optionally performs a **second pass**, removing already-retrieved entities from each basket and re-querying for the remainder.
|
|
284
268
|
4. Enriches each chunk with `entity_ids` and `primary_entity_id`, and returns the raw documents.
|
|
285
269
|
|
|
270
|
+
Every search request is sent with `response_detail: "extended"`, so each document includes its filer in `reporting_entity` (a list of entity IDs). The filer is used as a chunk's `entity_ids` when the chunk has no detections, and the second pass counts it as an entity already retrieved.
|
|
271
|
+
|
|
286
272
|
## API Reference
|
|
287
273
|
|
|
288
274
|
### `plan_search()`
|
|
@@ -324,7 +310,8 @@ Step 2: EXECUTION
|
|
|
324
310
|
### Helper Functions
|
|
325
311
|
|
|
326
312
|
- `deduplicate_documents(documents)` -- merge duplicate documents by `id`, combining their chunks.
|
|
327
|
-
- `convert_to_dataframe(raw_results)` -- convert documents to a DataFrame with one row per chunk.
|
|
313
|
+
- `convert_to_dataframe(raw_results)` -- convert documents to a DataFrame with one row per chunk. The `reporting_entities` column holds the document's filer ids.
|
|
314
|
+
- `get_reporting_entities(document)` -- return the filer ids of a search document (`reporting_entity`; the legacy `reporting_entities` / `reportingEntities` keys are also accepted).
|
|
328
315
|
- `resolve_universe(universe)` -- load entity IDs from a CSV path or validate a `list[str]`.
|
|
329
316
|
- `load_universe_from_csv(csv_path)` -- load entity IDs from a CSV only.
|
|
330
317
|
- `save_plan(plan, path)` / `load_plan(path)` -- persist plans as JSON.
|
|
@@ -70,28 +70,28 @@ def main() -> None:
|
|
|
70
70
|
print(f"% overlapping chunks: {(len(merged[merged._merge!='both'])/2)/len(merged)*100:.2f}%")
|
|
71
71
|
print('a')
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
73
|
+
if False:
|
|
74
|
+
plan = load_plan("pricing_power_evidence.json")
|
|
75
|
+
# Set the query.ranking.params.content_diversification to False
|
|
76
|
+
for basket in plan['baskets']:
|
|
77
|
+
basket['query']['ranking_params']['content_diversification']['enabled'] = False
|
|
78
|
+
#Remove sentiment filter, do not set it to None, remove the key instead
|
|
79
|
+
basket['query']['filters'].pop('sentiment', None)
|
|
80
|
+
|
|
81
|
+
# Keep only basket basket_6_high_20250924_20251022
|
|
82
|
+
plan['baskets'] = [basket for basket in plan['baskets'] if basket['basket_id'] == "basket_6_high_20250924_20251022"]
|
|
83
|
+
results_raw = execute_search(
|
|
84
|
+
search_plan=plan,
|
|
85
|
+
chunk_percentage=1,
|
|
86
|
+
requests_per_minute=450,
|
|
87
|
+
basket_filtered_entities=True,
|
|
88
|
+
)
|
|
89
89
|
|
|
90
90
|
# Store the results in a file but in parquet format
|
|
91
91
|
import json
|
|
92
92
|
import pandas as pd
|
|
93
|
-
df = pd.DataFrame(results_raw)
|
|
94
|
-
df.to_parquet(f"results_raw_{time.time()}_100pct_basket_6_without_sentiment.parquet")
|
|
93
|
+
#df = pd.DataFrame(results_raw)
|
|
94
|
+
#df.to_parquet(f"results_raw_{time.time()}_100pct_basket_6_without_sentiment.parquet")
|
|
95
95
|
|
|
96
96
|
plans = []
|
|
97
97
|
results_dfs = []
|
|
@@ -99,7 +99,7 @@ def main() -> None:
|
|
|
99
99
|
# Step 1: Plan the search
|
|
100
100
|
# Try to get plan from file
|
|
101
101
|
plan_t0 = time.perf_counter()
|
|
102
|
-
if False or Path("plan.json").is_file():
|
|
102
|
+
if False: #or Path("plan.json").is_file():
|
|
103
103
|
plan = load_plan("plan.json")
|
|
104
104
|
else:
|
|
105
105
|
plan = plan_search(
|
|
@@ -136,6 +136,8 @@ def main() -> None:
|
|
|
136
136
|
chunk_percentage=0.1,
|
|
137
137
|
requests_per_minute=450,
|
|
138
138
|
)
|
|
139
|
+
df = convert_to_dataframe(results_raw)
|
|
140
|
+
df.to_csv('example_out.csv',index=False)
|
|
139
141
|
search_elapsed_s = time.perf_counter() - search_t0
|
|
140
142
|
print(f"Search execution time: {search_elapsed_s:.3f}s")
|
|
141
143
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bigdata-smart-batching"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.5.0"
|
|
4
4
|
description = "High-performance semantic search with intelligent company grouping and parallel execution"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Bigdata.com", email = "support@ravenpack.com" }]
|
|
@@ -29,6 +29,14 @@ build-backend = "hatchling.build"
|
|
|
29
29
|
[tool.hatch.build.targets.wheel]
|
|
30
30
|
packages = ["src/bigdata_smart_batching"]
|
|
31
31
|
|
|
32
|
+
# Explicit allow-list: never ship untracked local files (.env, notes, scratch examples).
|
|
33
|
+
# Hatchling always adds pyproject.toml, README.md, LICENSE and .gitignore.
|
|
34
|
+
[tool.hatch.build.targets.sdist]
|
|
35
|
+
include = [
|
|
36
|
+
"src/",
|
|
37
|
+
"examples/example.py",
|
|
38
|
+
]
|
|
39
|
+
|
|
32
40
|
[project.urls]
|
|
33
41
|
homepage = "https://bigdata.com/api"
|
|
34
42
|
|
{bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/src/bigdata_smart_batching/__init__.py
RENAMED
|
@@ -24,6 +24,7 @@ from .search_function import (
|
|
|
24
24
|
build_volume_payload,
|
|
25
25
|
deduplicate_documents,
|
|
26
26
|
execute_search,
|
|
27
|
+
get_reporting_entities,
|
|
27
28
|
load_plan,
|
|
28
29
|
load_universe_from_csv,
|
|
29
30
|
plan_search,
|
|
@@ -51,6 +52,7 @@ __all__ = [
|
|
|
51
52
|
"convert_to_dataframe",
|
|
52
53
|
"deduplicate_documents",
|
|
53
54
|
"execute_search",
|
|
55
|
+
"get_reporting_entities",
|
|
54
56
|
"load_plan",
|
|
55
57
|
"load_universe_from_csv",
|
|
56
58
|
"plan_search",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
|
|
3
|
+
from .search_function import get_reporting_entities
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
def convert_to_dataframe(raw_results: list[dict]) -> pd.DataFrame:
|
|
5
7
|
"""
|
|
@@ -25,7 +27,7 @@ def convert_to_dataframe(raw_results: list[dict]) -> pd.DataFrame:
|
|
|
25
27
|
headline = raw_doc.get("headline", "")
|
|
26
28
|
timestamp = raw_doc.get("timestamp", "")
|
|
27
29
|
url = raw_doc.get("url", "")
|
|
28
|
-
reporting_entities = raw_doc
|
|
30
|
+
reporting_entities = get_reporting_entities(raw_doc)
|
|
29
31
|
|
|
30
32
|
# Extract source info
|
|
31
33
|
raw_source = raw_doc.get("source", {})
|
|
@@ -42,6 +42,8 @@ logger = logging.getLogger(__name__)
|
|
|
42
42
|
DEFAULT_API_BASE_URL = os.getenv("BIGDATA_API_BASE_URL", "https://api.bigdata.com")
|
|
43
43
|
SEARCH_ENDPOINT = "/v1/search"
|
|
44
44
|
COMENTION_ENDPOINT = "/v1/search/co-mentions/entities"
|
|
45
|
+
# Only "extended" returns the filer (``reporting_entity``). Bigdata API change of 2026-10-22.
|
|
46
|
+
SEARCH_RESPONSE_DETAIL = "extended"
|
|
45
47
|
MAX_ENTITIES_IN_ANY_OF = 500
|
|
46
48
|
MAX_CHUNKS_PER_BASKET = 1000
|
|
47
49
|
DEFAULT_REQUESTS_PER_MINUTE = int(os.getenv("BIGDATA_REQUESTS_PER_MINUTE", "450"))
|
|
@@ -1114,6 +1116,22 @@ def exponential_backoff(
|
|
|
1114
1116
|
return max(0.0, min(delay, cap))
|
|
1115
1117
|
|
|
1116
1118
|
|
|
1119
|
+
def get_reporting_entities(document: dict) -> list[str]:
|
|
1120
|
+
"""Filer ids of a /v1/search document.
|
|
1121
|
+
|
|
1122
|
+
The API returns them as ``reporting_entity`` (a list) only with
|
|
1123
|
+
``response_detail="extended"`` (since 2026-10-22). The legacy plural keys
|
|
1124
|
+
``reporting_entities`` / ``reportingEntities`` are still accepted.
|
|
1125
|
+
"""
|
|
1126
|
+
value = (
|
|
1127
|
+
document.get("reporting_entity")
|
|
1128
|
+
or document.get("reporting_entities")
|
|
1129
|
+
or document.get("reportingEntities")
|
|
1130
|
+
or []
|
|
1131
|
+
)
|
|
1132
|
+
return [e for e in (value if isinstance(value, list) else [value]) if e]
|
|
1133
|
+
|
|
1134
|
+
|
|
1117
1135
|
def make_search_request(
|
|
1118
1136
|
query: dict,
|
|
1119
1137
|
api_key: str,
|
|
@@ -1125,6 +1143,9 @@ def make_search_request(
|
|
|
1125
1143
|
"""
|
|
1126
1144
|
Make a search API request with rate limiting and retry logic.
|
|
1127
1145
|
|
|
1146
|
+
Always sends ``response_detail="extended"`` at the top level of the body
|
|
1147
|
+
(next to ``query``, not inside it) so documents include ``reporting_entity``.
|
|
1148
|
+
|
|
1128
1149
|
Args:
|
|
1129
1150
|
query: Complete query structure
|
|
1130
1151
|
api_key: API key
|
|
@@ -1139,7 +1160,7 @@ def make_search_request(
|
|
|
1139
1160
|
url = f"{api_base_url}{SEARCH_ENDPOINT}"
|
|
1140
1161
|
headers = {"X-API-KEY": api_key, "Content-Type": "application/json"}
|
|
1141
1162
|
|
|
1142
|
-
payload = {"query": query}
|
|
1163
|
+
payload = {"query": query, "response_detail": SEARCH_RESPONSE_DETAIL}
|
|
1143
1164
|
|
|
1144
1165
|
retry_count = 0
|
|
1145
1166
|
while retry_count < max_retries:
|
|
@@ -1368,12 +1389,8 @@ def execute_search(
|
|
|
1368
1389
|
for document in documents:
|
|
1369
1390
|
document_chunks = document.get("chunks", [])
|
|
1370
1391
|
total_chunks += len(document_chunks)
|
|
1371
|
-
#
|
|
1372
|
-
reporting_entities = (
|
|
1373
|
-
document.get("reporting_entities")
|
|
1374
|
-
or document.get("reportingEntities")
|
|
1375
|
-
or []
|
|
1376
|
-
)
|
|
1392
|
+
# Document-level filer ids (reporting_entity)
|
|
1393
|
+
reporting_entities = get_reporting_entities(document)
|
|
1377
1394
|
|
|
1378
1395
|
# Debug: Log first document and chunk structure
|
|
1379
1396
|
if not first_doc_logged and document_chunks:
|
|
@@ -1418,12 +1435,9 @@ def execute_search(
|
|
|
1418
1435
|
if entity_id:
|
|
1419
1436
|
entity_ids.append(entity_id)
|
|
1420
1437
|
|
|
1421
|
-
#
|
|
1438
|
+
# Fall back to the document's filer (reporting_entity)
|
|
1422
1439
|
if not entity_ids and reporting_entities:
|
|
1423
|
-
|
|
1424
|
-
entity_ids = [e for e in reporting_entities if e]
|
|
1425
|
-
else:
|
|
1426
|
-
entity_ids = [reporting_entities] if reporting_entities else []
|
|
1440
|
+
entity_ids = list(reporting_entities)
|
|
1427
1441
|
|
|
1428
1442
|
# Store entity information (always set, even if empty)
|
|
1429
1443
|
if basket_filtered_entities:
|
|
@@ -1503,7 +1517,7 @@ def execute_search(
|
|
|
1503
1517
|
for chunk in i.get("chunks", [])
|
|
1504
1518
|
for entity in chunk.get("entity_ids", [])
|
|
1505
1519
|
]
|
|
1506
|
-
+ [entity for i in all_documents for entity in i
|
|
1520
|
+
+ [entity for i in all_documents for entity in get_reporting_entities(i)]
|
|
1507
1521
|
+ [
|
|
1508
1522
|
chunk.get("primary_entity_id")
|
|
1509
1523
|
for i in all_documents
|
|
@@ -695,8 +695,8 @@ class SmartBatchingPlanner:
|
|
|
695
695
|
) -> dict:
|
|
696
696
|
"""POST through the shared rate limiter and concurrency semaphore.
|
|
697
697
|
|
|
698
|
-
Uses exponential backoff between retries on
|
|
699
|
-
network
|
|
698
|
+
Uses exponential backoff between retries on any non-200 HTTP status or
|
|
699
|
+
transient network error. All sleeps between retries happen *outside* the
|
|
700
700
|
concurrency semaphore so a backing-off thread doesn't pin a slot that
|
|
701
701
|
another worker could use.
|
|
702
702
|
|
|
@@ -711,8 +711,7 @@ class SmartBatchingPlanner:
|
|
|
711
711
|
Parsed JSON response on a 200 response.
|
|
712
712
|
|
|
713
713
|
Raises:
|
|
714
|
-
RuntimeError:
|
|
715
|
-
are exhausted.
|
|
714
|
+
RuntimeError: After retries are exhausted.
|
|
716
715
|
"""
|
|
717
716
|
for retry_count in range(max_retries):
|
|
718
717
|
retry_wait: float | None = None
|
|
@@ -728,22 +727,17 @@ class SmartBatchingPlanner:
|
|
|
728
727
|
if response.status_code == 200:
|
|
729
728
|
return response.json()
|
|
730
729
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
730
|
+
retry_wait = exponential_backoff(retry_count)
|
|
731
|
+
error_msg = f"API error {response.status_code}"
|
|
732
|
+
try:
|
|
733
|
+
error_details = response.json()
|
|
734
|
+
error_msg = (
|
|
735
|
+
f"{error_msg}\nResponse: {json.dumps(error_details, indent=2)}"
|
|
735
736
|
)
|
|
736
|
-
|
|
737
|
-
error_msg = f"
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
error_msg = f"{error_msg}\nResponse: {json.dumps(error_details, indent=2)}"
|
|
741
|
-
except ValueError:
|
|
742
|
-
error_msg = f"{error_msg}\nResponse: {response.text[:500]}"
|
|
743
|
-
raise RuntimeError(f"{error_prefix}: {error_msg}")
|
|
744
|
-
|
|
745
|
-
except RuntimeError:
|
|
746
|
-
raise
|
|
737
|
+
except ValueError:
|
|
738
|
+
error_msg = f"{error_msg}\nResponse: {response.text[:500]}"
|
|
739
|
+
last_error = error_msg
|
|
740
|
+
|
|
747
741
|
except requests.exceptions.Timeout as e:
|
|
748
742
|
retry_wait = exponential_backoff(retry_count)
|
|
749
743
|
last_error = str(e)
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project are documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## [1.4.0] - 2026-05-20
|
|
10
|
-
|
|
11
|
-
### Added
|
|
12
|
-
|
|
13
|
-
- `execute_search(..., basket_filtered_entities=True)`: when enabled, each chunk's `entity_ids` list is filtered to only include entity IDs present in the basket's `query.filters.entity.any_of` list, removing co-mentioned entities that were not explicitly targeted by the basket. Entity IDs are always deduplicated.
|
|
14
|
-
|
|
15
|
-
## [1.3.1] - 2026-05-19
|
|
16
|
-
|
|
17
|
-
### Fixed
|
|
18
|
-
|
|
19
|
-
- Now we always retry requests, before we were only retrying on 429 and 403 errors.
|
|
20
|
-
|
|
21
|
-
## [1.3.0] - 2026-05-12
|
|
22
|
-
|
|
23
|
-
### Added
|
|
24
|
-
|
|
25
|
-
- Document-level **source** filters: `INCLUDE` / `EXCLUDE` with a non-empty list of source ID strings, validated with `validate_source_filter`, modeled as `SourceFilter` / `SourceFilterInput` with `SourceMode` (type alias of `CategoryMode`). Serialized to `query.filters.source` on co-mention, volume, and search payloads. `plan_search(..., source=...)` and `source_filter` plumbed through `SmartBatchingPlanner` co-mention and volume flows. Payload builders `build_comention_payload`, `build_volume_payload`, and `build_search_query` accept optional `source_filter`. Types and validator re-exported from the package root.
|
|
26
|
-
|
|
27
|
-
## [1.2.1] - 2026-05-12
|
|
28
|
-
|
|
29
|
-
### Fixed
|
|
30
|
-
|
|
31
|
-
- Modified README.md file to add latest changes
|
|
32
|
-
|
|
33
|
-
## [1.2.0] - 2026-05-12
|
|
34
|
-
|
|
35
|
-
### Added
|
|
36
|
-
|
|
37
|
-
- Document-level **sentiment** filters: inclusive `min` / `max` range(s), validated with `validate_sentiment_range`, modeled as `SentimentRange` / `SentimentRangeInput`, serialized to `query.filters.sentiment.ranges` on co-mention, volume, and search payloads. `plan_search(..., sentiment=...)` and `sentiment_filter` plumbed through `SmartBatchingPlanner` co-mention and volume flows. Types and validator re-exported from the package root.
|
|
38
|
-
- **Planning throughput controls**: `plan_search(..., requests_per_minute=..., max_workers=...)` and `SmartBatchingPlanner(..., requests_per_minute=..., max_workers=..., rate_limiter=..., concurrency_limiter=...)` so all planner HTTP work shares one sliding-window rate limiter and one concurrency semaphore across internal thread pools.
|
|
39
|
-
- **`exponential_backoff`** helper in `search_function` (used by retry paths; capped via `MAX_RETRY_BACKOFF_SECONDS`).
|
|
40
|
-
|
|
41
|
-
### Changed
|
|
42
|
-
|
|
43
|
-
- **Retries:** co-mention / volume POST retries now use **shared exponential backoff** for `429`, `403`, timeouts, and other failures, instead of parsing `Retry-After` for `429` and separate ad-hoc `403` handling (constant renamed/clamped to `MAX_RETRY_BACKOFF_SECONDS`).
|
|
44
|
-
- **Volume time-series merge:** when aggregating batch results per date, a **`sentiment`** value on each point is the arithmetic mean of batch sentiments for that date (batches without sentiment for that date are omitted from the mean).
|
|
45
|
-
- **`SlidingWindowRateLimiter`:** window accounting aligned with **ceil-based** `max_per_window` expectations (tests updated for low rates and 450 rpm-style cases).
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
## [1.1.0] - 2026-04-20
|
|
50
|
-
|
|
51
|
-
### Added
|
|
52
|
-
|
|
53
|
-
- `convert_to_dataframe` now includes a `detections` column: for each chunk row,
|
|
54
|
-
entity detections are taken from `chunk["detections"]` (entries with
|
|
55
|
-
`type == "entity"`); rows without chunks use an empty list.
|
|
56
|
-
|
|
57
|
-
## [1.0.2] - 2026-04-17
|
|
58
|
-
|
|
59
|
-
### Fixed
|
|
60
|
-
|
|
61
|
-
- Corrected README file
|
|
62
|
-
|
|
63
|
-
## [1.0.1] - 2026-04-17
|
|
64
|
-
|
|
65
|
-
### Fixed
|
|
66
|
-
|
|
67
|
-
- Republish on PyPI after an erroneous publish of v1.0.0; package contents match the intended release.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
## [1.0.0] - 2026-04-17
|
|
71
|
-
|
|
72
|
-
First stable release. Redesigns the batching pipeline so every API request stays
|
|
73
|
-
within service limits while keeping plans deterministic.
|
|
74
|
-
|
|
75
|
-
### Added
|
|
76
|
-
|
|
77
|
-
- `create_entity_groups` step: sorts companies by chunks desc and greedy-packs
|
|
78
|
-
under `MAX_CHUNKS_PER_BASKET` and `MAX_ENTITIES_IN_ANY_OF`, honoring
|
|
79
|
-
`min_entities_per_basket`, with tail-group merge when it fits the entity
|
|
80
|
-
limit.
|
|
81
|
-
- Automatic splitting of co-mention volume queries into sub-ranges of up to 365
|
|
82
|
-
days, with per-company chunks summed across sub-ranges in both three-pass and
|
|
83
|
-
iterative modes. Iterative mode now fans out `(batch, sub-range)` work items
|
|
84
|
-
to the thread pool and derives `very_low` from companies never seen in any
|
|
85
|
-
sub-range.
|
|
86
|
-
- Parallel execution of `get_volume_timeseries` via `ThreadPoolExecutor`.
|
|
87
|
-
- `volume_bucket` metadata on every group/basket via `classify_volume_bucket`
|
|
88
|
-
and `majority_volume_bucket` (ties broken by `VOLUME_BUCKET_PRIORITY`).
|
|
89
|
-
- Centralized `VOLUME_BUCKETS` in `smart_batching_config` with explicit
|
|
90
|
-
`[lower, upper)` semantics and new `VOLUME_BUCKET_PRIORITY` constant.
|
|
91
|
-
- INCLUDE/EXCLUDE category filtering for planning and all Bigdata query paths
|
|
92
|
-
(co-mention, volume, search), validated against a fixed set of allowed
|
|
93
|
-
category strings. `CategoryFilter`, `CategoryMode`, and validators/builders
|
|
94
|
-
are exported from the package.
|
|
95
|
-
- Shared payload builders `build_comention_payload`, `build_volume_payload`,
|
|
96
|
-
and `build_search_query`, which `SmartBatchingPlanner` now routes through
|
|
97
|
-
instead of constructing payloads inline.
|
|
98
|
-
- `content_diversification` option in `ranking_params`, exposed on
|
|
99
|
-
`plan_search`.
|
|
100
|
-
- Universe input accepts either a CSV path or an inline list of entity IDs
|
|
101
|
-
(`universe: str | list[str]`). Added `resolve_universe()` and `UniverseInput`,
|
|
102
|
-
both exported from the package.
|
|
103
|
-
- `Makefile` test targets and coverage settings in `pyproject.toml`.
|
|
104
|
-
|
|
105
|
-
### Changed
|
|
106
|
-
|
|
107
|
-
- **Breaking:** `SmartBatchingPlanner` public API redesigned. `create_baskets`
|
|
108
|
-
is replaced by `create_entity_groups` (new signature:
|
|
109
|
-
`min_entities_per_group` instead of `max_chunks`/`min_entities_per_basket`);
|
|
110
|
-
returned dicts no longer include `basket_id` or `volume_range` and expose
|
|
111
|
-
`volume_bucket` instead.
|
|
112
|
-
- **Breaking:** Removed helpers `group_by_volume`,
|
|
113
|
-
`consolidate_period_groups`, `sub_period_volumes_from_series`, and
|
|
114
|
-
`get_volume_timeseries_per_company`.
|
|
115
|
-
- **Breaking:** `very_low` basket IDs are now
|
|
116
|
-
`basket_{n}_very_low_{start}_{end}` instead of `very_low_basket_{n}`;
|
|
117
|
-
downstream parsers must be updated.
|
|
118
|
-
- **Breaking:** `VOLUME_BUCKETS` ranges are now enforced as `[lower, upper)`.
|
|
119
|
-
- **Breaking:** `plan_search(..., universe_csv_path=)` renamed to
|
|
120
|
-
`universe=` (accepts `str | list[str]`). `SmartBatchingPlanner.load_universe`
|
|
121
|
-
and `plan_all_periods` now take a required `universe` argument and delegate
|
|
122
|
-
to `resolve_universe`; `UNIVERSE_CSV_PATH` and duplicate CSV parsing removed.
|
|
123
|
-
- **Breaking:** `plan_search` / `plan_all_periods` now accept an optional
|
|
124
|
-
`text` parameter and parameter order has been reordered.
|
|
125
|
-
- Entity filters now use `search_in=BODY` for co-mention and volume payloads;
|
|
126
|
-
`ranking_params` dropped from the volume query.
|
|
127
|
-
- Omit `text` from co-mention, volume, and search payloads when topic/text is
|
|
128
|
-
`None`.
|
|
129
|
-
- Planned baskets are sorted by `basket_id` for stable ordering.
|
|
130
|
-
- Architecture documentation updated to reflect the new `plan_search` flow
|
|
131
|
-
(sub-range splitting, entity groups, pre-fetch only for heavy groups, one
|
|
132
|
-
basket per `(group, sub-period)`) and a new high-level flow diagram.
|
|
133
|
-
- `load_universe_from_csv` rejects empty CSV paths.
|
|
134
|
-
|
|
135
|
-
### Removed
|
|
136
|
-
|
|
137
|
-
- Obsolete `VOLUME_BUCKETS` structure test.
|
|
138
|
-
|
|
139
|
-
## [0.2.0] - 2026-04-14
|
|
140
|
-
|
|
141
|
-
### Changed
|
|
142
|
-
|
|
143
|
-
- **Breaking:** `plan_search` now returns `chunk_upper_bound_estimate` instead of `total_expected_chunks`. Callers, saved plans, and any code reading the plan dict must use the new key. `execute_search` reads `chunk_upper_bound_estimate` when deriving sampling from the plan.
|
|
144
|
-
- Terminology and docs describe this value as a chunk upper bound estimate (module docstring, README, architecture diagram, and example output).
|
|
145
|
-
- README installation: document PyPI consumption (`pip install bigdata-smart-batching`, `uv add bigdata-smart-batching`) and keep a separate **Development** section for cloning the repo and `uv sync`.
|
|
146
|
-
|
|
147
|
-
### Fixed
|
|
148
|
-
|
|
149
|
-
- Align `uv.lock` `requires-python` with `pyproject.toml` (`>=3.11,<4.0`).
|
|
150
|
-
|
|
151
|
-
## [0.1.0] - 2026-04-14
|
|
152
|
-
|
|
153
|
-
### Added
|
|
154
|
-
|
|
155
|
-
- Initial release of **bigdata-smart-batching**: high-performance semantic search with intelligent company grouping and parallel execution.
|
|
156
|
-
- Planning API (`plan_search`) to organize searches via smart batching and report expected chunk counts.
|
|
157
|
-
- Execution API (`execute_search`) with rate-limited parallel requests and proportional sampling to preserve result distribution.
|
|
158
|
-
- Utilities: document deduplication, DataFrame conversion, and configurable search/batching behavior via `smart_batching` and related modules.
|
|
159
|
-
- Python 3.11+ support; dependencies include `requests`, `pandas`, `numpy`, `matplotlib`, and `python-dotenv`.
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
.PHONY: lint lint-check format format-check test test-cov
|
|
2
|
-
|
|
3
|
-
lint:
|
|
4
|
-
uvx ruff check --fix src/ tests/
|
|
5
|
-
|
|
6
|
-
lint-check:
|
|
7
|
-
uvx ruff check --extend-select I src/ tests/
|
|
8
|
-
|
|
9
|
-
format:
|
|
10
|
-
uvx ruff format src/ tests/
|
|
11
|
-
|
|
12
|
-
format-check:
|
|
13
|
-
uvx ruff format --check src/ tests/
|
|
14
|
-
|
|
15
|
-
test:
|
|
16
|
-
uv run pytest
|
|
17
|
-
|
|
18
|
-
test-cov:
|
|
19
|
-
uv run pytest --cov --cov-report=term-missing --cov-report=html
|