🐛 Bug Description
PR Newswire collection fetches every discovered article before returning that the listing window is incomplete. A downstream caller that bisects incomplete windows therefore discards an expensive parent result and downloads the same articles again. Article fetching is the measured bottleneck, and its per-host concurrency is currently fixed in the private fetch policy rather than configurable through the public NewsCollectionCfg.
🔄 Steps to Reproduce
- Call
collect_news(NewsWindow(source="pr-newswire", start=..., end=...)) with a window whose listing exceeds the current 20-page × 100-row discovery budget.
- Observe that
_discover_with_fetcher reaches the page budget and produces complete=false.
- Observe that
_collect_pr_newswire still runs asyncio.gather over every discovered observation before returning the incomplete NewsBatch.
- Have a completeness-preserving caller bisect the incomplete window and retry its children; the parent articles are fetched again because the incomplete parent documents cannot prove full coverage.
✅ Expected Behavior
An incomplete discovery result should be returned before expensive article fetching, or the public operation should otherwise provide a reusable staged result that prevents callers from downloading the same articles again during bisection. The public collection config should also provide a validated way to tune bounded per-host article-fetch concurrency for live-source workloads.
❌ Actual Behavior
The collector can download up to 2,000 article bodies at the fixed per-host concurrency of 2, then return complete=false. A caller that correctly retries smaller windows repeats those article downloads. In LLMQuant Data PR #696, a 14-day recovery window exposed this behavior; the downstream mitigation is to prepartition the requested range into windows of at most 24 hours before retaining the existing fail-closed bisection.
📋 Environment
- QuantMind Version: git
aafa8904
- Python Version: 3.11.13
- Operating System: macOS 15.6 arm64
- Installation Method: uv, git-pinned dependency from source
📝 Additional Context
A read-only benchmark used the fixed window 2026-08-06T12:00:00Z → 2026-08-06T14:00:00Z with 252 observed articles. Each run returned 252 documents, complete=true, and zero failures. Wall times were: concurrency 2 = 57.279s; concurrency 4 = 44.237s and 92.277s; concurrency 8 = 55.562s and 48.548s. This shows that 8 was stable in both samples, while source latency remained variable; the benchmark changed the private _DEFAULT_FETCH_POLICY only for measurement and is not a suitable production integration.
Error Logs
No request failures were observed in the bounded benchmark. The defect is redundant successful article fetching, not an exception.
Configuration
listing_page_size: 100
listing_max_pages: 20
current_max_concurrency_per_host: 2
downstream_recovery_max_window_seconds: 86400
🛠️ Possible Solution
Options for maintainer discussion: separate discovery completeness from article collection; return immediately without article fetching when discovery is incomplete; or expose a reusable staged discovery artifact. Independently, add a validated public NewsCollectionCfg field for bounded per-host fetch concurrency and thread it through collect_news to the source fetch policy instead of requiring callers to touch private module state.
Checklist
🐛 Bug Description
PR Newswire collection fetches every discovered article before returning that the listing window is incomplete. A downstream caller that bisects incomplete windows therefore discards an expensive parent result and downloads the same articles again. Article fetching is the measured bottleneck, and its per-host concurrency is currently fixed in the private fetch policy rather than configurable through the public
NewsCollectionCfg.🔄 Steps to Reproduce
collect_news(NewsWindow(source="pr-newswire", start=..., end=...))with a window whose listing exceeds the current 20-page × 100-row discovery budget._discover_with_fetcherreaches the page budget and producescomplete=false._collect_pr_newswirestill runsasyncio.gatherover every discovered observation before returning the incompleteNewsBatch.✅ Expected Behavior
An incomplete discovery result should be returned before expensive article fetching, or the public operation should otherwise provide a reusable staged result that prevents callers from downloading the same articles again during bisection. The public collection config should also provide a validated way to tune bounded per-host article-fetch concurrency for live-source workloads.
❌ Actual Behavior
The collector can download up to 2,000 article bodies at the fixed per-host concurrency of 2, then return
complete=false. A caller that correctly retries smaller windows repeats those article downloads. In LLMQuant Data PR #696, a 14-day recovery window exposed this behavior; the downstream mitigation is to prepartition the requested range into windows of at most 24 hours before retaining the existing fail-closed bisection.📋 Environment
aafa8904📝 Additional Context
A read-only benchmark used the fixed window
2026-08-06T12:00:00Z → 2026-08-06T14:00:00Zwith 252 observed articles. Each run returned 252 documents,complete=true, and zero failures. Wall times were: concurrency 2 = 57.279s; concurrency 4 = 44.237s and 92.277s; concurrency 8 = 55.562s and 48.548s. This shows that 8 was stable in both samples, while source latency remained variable; the benchmark changed the private_DEFAULT_FETCH_POLICYonly for measurement and is not a suitable production integration.Error Logs
Configuration
🛠️ Possible Solution
Options for maintainer discussion: separate discovery completeness from article collection; return immediately without article fetching when discovery is incomplete; or expose a reusable staged discovery artifact. Independently, add a validated public
NewsCollectionCfgfield for bounded per-host fetch concurrency and thread it throughcollect_newsto the source fetch policy instead of requiring callers to touch private module state.Checklist