bigdata-smart-batching 1.3.1__tar.gz → 1.4.1__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.
Files changed (23) hide show
  1. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/.gitignore +2 -0
  2. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/CHANGELOG.md +14 -2
  3. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/PKG-INFO +3 -2
  4. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/README.md +2 -1
  5. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/examples/example.py +48 -0
  6. bigdata_smart_batching-1.4.1/examples/example_non_determinism.py +97 -0
  7. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/pyproject.toml +1 -1
  8. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/src/bigdata_smart_batching/search_function.py +13 -1
  9. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/src/bigdata_smart_batching/smart_batching.py +13 -19
  10. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/uv.lock +1 -1
  11. bigdata_smart_batching-1.3.1/plan.json +0 -20473
  12. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/LICENSE +0 -0
  13. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/Makefile +0 -0
  14. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/docs/smart-batching-architecture.md +0 -0
  15. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/examples/example_overlapping.py +0 -0
  16. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/src/bigdata_smart_batching/__init__.py +0 -0
  17. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/src/bigdata_smart_batching/output_converter.py +0 -0
  18. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/src/bigdata_smart_batching/smart_batching_config.py +0 -0
  19. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/tests/__init__.py +0 -0
  20. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/tests/test_config.py +0 -0
  21. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/tests/test_output_converter.py +0 -0
  22. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/tests/test_rate_limiter.py +0 -0
  23. {bigdata_smart_batching-1.3.1 → bigdata_smart_batching-1.4.1}/tests/test_validation.py +0 -0
@@ -110,3 +110,5 @@ Thumbs.db
110
110
  *.csv
111
111
 
112
112
  benchmark/
113
+ *.parquet
114
+ *.json
@@ -6,11 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
 
9
- ## [1.3.1] - 2026-05-18
9
+ ## [1.4.1] - 2026-06-02
10
+
11
+ ### Fixed
12
+
13
+ - `SmartBatchingPlanner._post_with_retry`: retry with exponential backoff on any non-200 HTTP status or transient network error. Previously only `429` and `403` were retried; other statuses (e.g. `500`) failed immediately.
14
+
15
+ ## [1.4.0] - 2026-05-20
10
16
 
11
17
  ### Added
12
18
 
13
- - 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.
19
+ - `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.
20
+
21
+ ## [1.3.1] - 2026-05-19
22
+
23
+ ### Fixed
24
+
25
+ - Now we always retry requests, before we were only retrying on 429 and 403 errors.
14
26
 
15
27
  ## [1.3.0] - 2026-05-12
16
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bigdata-smart-batching
3
- Version: 1.3.1
3
+ Version: 1.4.1
4
4
  Summary: High-performance semantic search with intelligent company grouping and parallel execution
5
5
  Project-URL: homepage, https://bigdata.com/api
6
6
  Author-email: "Bigdata.com" <support@ravenpack.com>
@@ -319,6 +319,7 @@ Step 2: EXECUTION
319
319
  | `max_workers` | `int` | `40` | Parallel workers |
320
320
  | `overwrite_chunks_per_basket` | `int \| None` | `None` | Override the proportional chunk allocation |
321
321
  | `second_pass` | `bool` | `False` | Re-query each basket for entities not yet retrieved |
322
+ | `basket_filtered_entities` | `bool` | `False` | When `True`, each chunk's `entity_ids` is filtered to only include IDs present in the basket's `query.filters.entity.any_of` list; entity IDs are always deduplicated |
322
323
 
323
324
  ### Helper Functions
324
325
 
@@ -417,7 +418,7 @@ Ties are resolved using `VOLUME_BUCKET_PRIORITY`: `high > medium > low > very_lo
417
418
  ## Changelog
418
419
 
419
420
  See [CHANGELOG.md](CHANGELOG.md) for release notes. The package is currently at
420
- version **1.2.0**.
421
+ version **1.4.0**.
421
422
 
422
423
  ## License
423
424
 
@@ -303,6 +303,7 @@ Step 2: EXECUTION
303
303
  | `max_workers` | `int` | `40` | Parallel workers |
304
304
  | `overwrite_chunks_per_basket` | `int \| None` | `None` | Override the proportional chunk allocation |
305
305
  | `second_pass` | `bool` | `False` | Re-query each basket for entities not yet retrieved |
306
+ | `basket_filtered_entities` | `bool` | `False` | When `True`, each chunk's `entity_ids` is filtered to only include IDs present in the basket's `query.filters.entity.any_of` list; entity IDs are always deduplicated |
306
307
 
307
308
  ### Helper Functions
308
309
 
@@ -401,7 +402,7 @@ Ties are resolved using `VOLUME_BUCKET_PRIORITY`: `high > medium > low > very_lo
401
402
  ## Changelog
402
403
 
403
404
  See [CHANGELOG.md](CHANGELOG.md) for release notes. The package is currently at
404
- version **1.2.0**.
405
+ version **1.4.0**.
405
406
 
406
407
  ## License
407
408
 
@@ -45,6 +45,54 @@ def main() -> None:
45
45
  )
46
46
  sys.exit(1)
47
47
 
48
+ if False:
49
+ #Compare the results_raw parquet files to see if they are the same but just counting the number of ids
50
+ import pandas as pd
51
+ files = [x for x in os.listdir(".") if x.startswith("results_raw_") and x.endswith("_100pct_basket_6_without_sentiment.parquet")]
52
+ results_dfs = []
53
+ for file in files:
54
+ df = pd.read_parquet(file)
55
+ df_rows = []
56
+ for row in df.to_dict(orient="records"):
57
+ df_rows.append({
58
+ 'doc_id': row['id'],
59
+ 'chunk_cnum': [i['cnum'] for i in row['chunks']],
60
+ })
61
+ results_temp = pd.DataFrame(df_rows)
62
+ # Expand chunks into rows keeping document id, and chunk number
63
+ results_temp = results_temp.explode('chunk_cnum')
64
+ results_temp = results_temp.drop_duplicates()
65
+ #results_temp.sort_values(by=['doc_id', 'chunk_cnum']).to_csv(f"results_raw_{file.split('_')[2]}_100pct_basket_6.csv", index=False)
66
+ results_dfs.append(results_temp)
67
+
68
+ # Merge using id as the key
69
+ merged = results_dfs[0].merge(results_dfs[1], on=['doc_id', 'chunk_cnum'], how='outer', indicator=True)
70
+ print(f"% overlapping chunks: {(len(merged[merged._merge!='both'])/2)/len(merged)*100:.2f}%")
71
+ print('a')
72
+
73
+
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
+
90
+ # Store the results in a file but in parquet format
91
+ import json
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")
95
+
48
96
  plans = []
49
97
  results_dfs = []
50
98
  for i in range(1):
@@ -0,0 +1,97 @@
1
+ """Execute a single Bigdata search request for basket_6_high_20250924_20251022.
2
+
3
+ Uses only the requests library (no smart-batching helpers). Run twice and compare
4
+ responses to observe search non-determinism.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import pandas as pd
10
+ import requests
11
+
12
+ SEARCH_ENDPOINT = "/v1/search"
13
+ DEFAULT_API_BASE_URL = "https://api.bigdata.com"
14
+
15
+ QUERY = {
16
+ "auto_enrich_filters": False,
17
+ "filters": {
18
+ "timestamp": {
19
+ "start": "2025-09-24T00:00:00Z",
20
+ "end": "2025-10-22T23:59:59Z"
21
+ },
22
+ "entity": {
23
+ "any_of": [
24
+ "12A3A3",
25
+ "619882",
26
+ "9196A2",
27
+ "58CA9A",
28
+ "50070E",
29
+ "E8846E",
30
+ "E09E2B",
31
+ "990AD0",
32
+ "4A6F00",
33
+ "0157B1"
34
+ ],
35
+ "search_in": "BODY"
36
+ },
37
+ "category": {
38
+ "mode": "INCLUDE",
39
+ "values": [
40
+ "news_premium",
41
+ "transcripts"
42
+ ]
43
+ },
44
+ "source": {
45
+ "mode": "EXCLUDE",
46
+ "values": [
47
+ "DFF004"
48
+ ]
49
+ },
50
+ "sentiment": {"ranges": [{"min": 0.1,"max": 1.0}]}
51
+ },
52
+ "ranking_params": {
53
+ "source_boost": 1,
54
+ "freshness_boost": 0,
55
+ "reranker": {
56
+ "enabled": False
57
+ },
58
+ "content_diversification": {"enabled": True}
59
+ },
60
+ "max_chunks": 1000,
61
+ "text": "The company demonstrates pricing power, raises prices without losing volume, or maintains price discipline against competitors."
62
+ }
63
+
64
+ # Query from pricing_power_evidence.json plan, basket basket_6_high_20250924_20251022
65
+
66
+
67
+ def main() -> None:
68
+
69
+ api_key = "bd_v1_UPdNGgq44wBO9XR8THfiGvHd7Km9TCIvDmnT5AiatA9T56"
70
+ url = "https://api.bigdata.com/v1/search"
71
+ headers = {"X-API-KEY": api_key, "Content-Type": "application/json"}
72
+ payload = {"query": QUERY}
73
+
74
+ # Execute the query 10 times and store the results in a df, counting the number of times a doc_id and chunk_cnum appears
75
+ dfs = []
76
+ for i in range(2):
77
+ df_rows = []
78
+ response = requests.post(url, headers=headers, json=payload, timeout=120)
79
+ results = response.json().get("results", [])
80
+ for result in results:
81
+ df_rows.append({
82
+ 'doc_id': result['id'],
83
+ 'chunk_cnum': [i['cnum'] for i in result['chunks']],
84
+ })
85
+ df = pd.DataFrame(df_rows)
86
+ # Expand chunks into rows keeping document id, and chunk number
87
+ df = df.explode('chunk_cnum')
88
+ df = df.drop_duplicates().reset_index(drop=True)
89
+ dfs.append(df)
90
+
91
+ all_df = pd.concat(dfs)
92
+ all_df = all_df.groupby(['doc_id', 'chunk_cnum']).size().reset_index(name='count')
93
+ print(all_df)
94
+
95
+
96
+ if __name__ == "__main__":
97
+ main()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bigdata-smart-batching"
3
- version = "1.3.1"
3
+ version = "1.4.1"
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" }]
@@ -1247,6 +1247,7 @@ def execute_search(
1247
1247
  second_pass: bool = False,
1248
1248
  rate_limiter: SlidingWindowRateLimiter | None = None,
1249
1249
  concurrency_limiter: ConcurrencySemaphore | None = None,
1250
+ basket_filtered_entities: bool = False,
1250
1251
  ) -> list[dict]:
1251
1252
  """
1252
1253
  Execute search with proportional sampling.
@@ -1270,6 +1271,9 @@ def execute_search(
1270
1271
  concurrency_limiter: Optional pre-built :class:`ConcurrencySemaphore` to
1271
1272
  share with other components. When provided, ``max_workers`` controls
1272
1273
  only the executor's worker count for basket fan-out.
1274
+ basket_filtered_entities: If True, each chunk's ``entity_ids`` will be
1275
+ filtered to only include entity IDs present in the basket's
1276
+ ``query.filters.entity.any_of`` list.
1273
1277
 
1274
1278
  Returns:
1275
1279
  List of document dictionaries (each containing a chunks array).
@@ -1422,7 +1426,15 @@ def execute_search(
1422
1426
  entity_ids = [reporting_entities] if reporting_entities else []
1423
1427
 
1424
1428
  # Store entity information (always set, even if empty)
1425
- chunk["entity_ids"] = entity_ids
1429
+ if basket_filtered_entities:
1430
+ basket_entity_ids = set(
1431
+ basket_info.get("query", {})
1432
+ .get("filters", {})
1433
+ .get("entity", {})
1434
+ .get("any_of", [])
1435
+ )
1436
+ entity_ids = [eid for eid in entity_ids if eid in basket_entity_ids]
1437
+ chunk["entity_ids"] = list(set(entity_ids))
1426
1438
  chunk["primary_entity_id"] = entity_ids[0] if entity_ids else None
1427
1439
 
1428
1440
  logger.info(
@@ -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 429, 403, and transient
699
- network errors. All sleeps between retries happen *outside* the
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: On a non-retriable HTTP error status, or after retries
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
- if response.status_code in (429, 403):
732
- retry_wait = exponential_backoff(retry_count)
733
- last_error = (
734
- f"API error {response.status_code}: {response.text[:500]}"
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
- else:
737
- error_msg = f"API error {response.status_code}"
738
- try:
739
- error_details = response.json()
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)
@@ -12,7 +12,7 @@ resolution-markers = [
12
12
 
13
13
  [[package]]
14
14
  name = "bigdata-smart-batching"
15
- version = "1.3.1"
15
+ version = "1.4.1"
16
16
  source = { editable = "." }
17
17
  dependencies = [
18
18
  { name = "matplotlib" },