{"record":{"id":"ea1e008b22bdc9d2","repo":"ZhuLinsen/daily_stock_analysis","slug":"phase-filtered-results-match-too-many-rows-narrow","errorCode":null,"errorMessage":"Phase-filtered results match too many rows; narrow the analysis date range or stock code.","messagePattern":"Phase-filtered results match too many rows; narrow the analysis date range or stock code\\.","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"warning","filePath":"src/services/backtest_service.py","lineNumber":737,"sourceCode":"        scanned = 0\n        matched_total = 0\n        page_rows: List[\n            Tuple[\n                BacktestResult,\n                Optional[str],\n                Optional[str],\n                Optional[Dict[str, Any]],\n                str,\n                Optional[str],\n                Optional[str],\n                Optional[int],\n            ]\n        ] = []\n\n        while True:\n            remaining_probe_rows = self.MAX_DYNAMIC_SUMMARY_ROWS + 1 - scanned\n            if remaining_probe_rows <= 0:\n                raise ValueError(\"Phase-filtered results match too many rows; narrow the analysis date range or stock code.\")\n            batch_limit = min(batch_size, remaining_probe_rows)\n            batch = self.repo.get_results_with_context_batch(\n                code=code,\n                eval_window_days=eval_window_days,\n                engine_version=engine_version,\n                analysis_date_from=analysis_date_from,\n                analysis_date_to=analysis_date_to,\n                days=None,\n                offset=sql_offset,\n                limit=batch_limit,\n            )\n            if not batch:\n                break\n            scanned += len(batch)\n            if scanned > self.MAX_DYNAMIC_SUMMARY_ROWS:\n                raise ValueError(\"Phase-filtered results match too many rows; narrow the analysis date range or stock code.\")\n            sql_offset += len(batch)\n            for (","sourceCodeStart":719,"sourceCodeEnd":755,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/backtest_service.py#L719-L755","documentation":"In the batched phase-filtered result listing, the service probes rows in batches while tracking a scanned budget of MAX_DYNAMIC_SUMMARY_ROWS+1. Before each batch it computes remaining_probe_rows; when the budget is exhausted before the phase filter matched enough rows, it raises this ValueError (HTTP 400). This protects the paginated results endpoint from unbounded scanning when a phase filter matches very few rows inside a huge candidate set.","triggerScenarios":"GET /api/v1/backtest/results?analysis_phase=unknown with a large unfiltered result set where matching-phase rows are sparse, so the scanner burns the whole probe budget on non-matching rows.","commonSituations":"Filtering for premarket/postmarket phases when most analyses were intraday; requesting the last page of a phase with few entries; legacy rows whose context_snapshot lacks phase info (bucketed as unknown among many).","solutions":["Narrow with analysis_date_from/analysis_date_to or a code filter so the candidate set fits the scan budget.","Drop the analysis_phase filter and use the plain paginated results endpoint, then filter client-side if the dataset is small.","Backfill/normalize context_snapshot phase data for legacy rows so phase filtering is selective."],"exampleFix":"# before\ndata = service.get_recent_evaluations(analysis_phase=\"premarket\")\n\n# after\ndata = service.get_recent_evaluations(\n    analysis_phase=\"premarket\",\n    analysis_date_from=\"2026-08-01\",\n    code=\"600519\",\n)","handlingStrategy":"validation","validationCode":"count = service.repo.count_results(code=code, eval_window_days=eval_window_days, engine_version=engine_version, analysis_date_from=dfrom, analysis_date_to=dto)\nif count > service.MAX_DYNAMIC_SUMMARY_ROWS:\n    return JSONResponse(status_code=400, content={\"error\": \"too_many_rows\", \"hint\": \"narrow date range or code\"})\ndata = service.get_recent_evaluations(code=code, analysis_phase=phase, analysis_date_from=dfrom, analysis_date_to=dto)","typeGuard":null,"tryCatchPattern":"try:\n    rows = service.get_recent_evaluations(analysis_phase=phase, analysis_date_from=dfrom, analysis_date_to=dto)\nexcept ValueError as exc:\n    if \"too many rows\" in str(exc):\n        return JSONResponse(status_code=400, content={\"error\": \"too_many_rows\", \"message\": str(exc)})\n    raise","preventionTips":["Phase filters are selective only when the target phase is well represented — check phase distribution before offering rare-phase filters unbounded.","Default phase-filtered views to a narrow date window.","Backfill phase info into legacy context_snapshots so filters match early."],"tags":["backtest","phase-filter","row-limit","pagination"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}