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.
Files changed (22) hide show
  1. bigdata_smart_batching-1.4.0/README.md → bigdata_smart_batching-1.5.0/PKG-INFO +20 -1
  2. bigdata_smart_batching-1.4.0/PKG-INFO → bigdata_smart_batching-1.5.0/README.md +4 -17
  3. {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/examples/example.py +21 -19
  4. {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/pyproject.toml +9 -1
  5. {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/src/bigdata_smart_batching/__init__.py +2 -0
  6. {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/src/bigdata_smart_batching/output_converter.py +3 -1
  7. {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/src/bigdata_smart_batching/search_function.py +27 -13
  8. {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/src/bigdata_smart_batching/smart_batching.py +13 -19
  9. bigdata_smart_batching-1.4.0/CHANGELOG.md +0 -159
  10. bigdata_smart_batching-1.4.0/Makefile +0 -19
  11. bigdata_smart_batching-1.4.0/docs/smart-batching-architecture.md +0 -481
  12. bigdata_smart_batching-1.4.0/examples/example_non_determinism.py +0 -97
  13. bigdata_smart_batching-1.4.0/examples/example_overlapping.py +0 -185
  14. bigdata_smart_batching-1.4.0/tests/__init__.py +0 -0
  15. bigdata_smart_batching-1.4.0/tests/test_config.py +0 -30
  16. bigdata_smart_batching-1.4.0/tests/test_output_converter.py +0 -82
  17. bigdata_smart_batching-1.4.0/tests/test_rate_limiter.py +0 -120
  18. bigdata_smart_batching-1.4.0/tests/test_validation.py +0 -342
  19. bigdata_smart_batching-1.4.0/uv.lock +0 -1045
  20. {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/.gitignore +0 -0
  21. {bigdata_smart_batching-1.4.0 → bigdata_smart_batching-1.5.0}/LICENSE +0 -0
  22. {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
- 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
- )
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.4.0"
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
 
@@ -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.get("reporting_entities", [])
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
- # Get document-level entity information (try both snake_case and camelCase)
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
- # Try document-level reporting_entities
1438
+ # Fall back to the document's filer (reporting_entity)
1422
1439
  if not entity_ids and reporting_entities:
1423
- if isinstance(reporting_entities, list):
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.get("reporting_entities", [])]
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 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)
@@ -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