diff --git a/api/v1/endpoints/analysis.py b/api/v1/endpoints/analysis.py index 5cd343f52..e8b886cff 100644 --- a/api/v1/endpoints/analysis.py +++ b/api/v1/endpoints/analysis.py @@ -885,6 +885,16 @@ def _prepare_report_for_task_enrichment( return enriched_report +def _first_non_empty_report_value(*values: Any) -> Any: + for value in values: + if value is None: + continue + if isinstance(value, str) and not value.strip(): + continue + return value + return None + + def _ensure_report_action_fields(report_data: Dict[str, Any]) -> Dict[str, Any]: enriched_report = dict(report_data) meta = dict(enriched_report.get("meta") or {}) @@ -899,7 +909,10 @@ def _ensure_report_action_fields(report_data: Dict[str, Any]) -> Dict[str, Any]: explicit_action=raw_result.get("action") or summary.get("action"), report_type=meta.get("report_type"), report_language=report_language, - sentiment_score=summary.get("sentiment_score") or raw_result.get("sentiment_score"), + sentiment_score=_first_non_empty_report_value( + summary.get("sentiment_score"), + raw_result.get("sentiment_score"), + ), guardrail_reason=_extract_guardrail_reason(raw_result), align_with_score=True, ) @@ -1339,10 +1352,10 @@ def _build_analysis_report( explicit_action=raw_result_data.get("action") or details_data.get("action") or summary_data.get("action"), report_type=meta.report_type, report_language=report_language, - sentiment_score=( - summary_data.get("sentiment_score") - or raw_result_data.get("sentiment_score") - or details_data.get("sentiment_score") + sentiment_score=_first_non_empty_report_value( + summary_data.get("sentiment_score"), + raw_result_data.get("sentiment_score"), + details_data.get("sentiment_score"), ), guardrail_reason=_extract_guardrail_reason(raw_result_data), align_with_score=True, diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index bac9dd591..ef3219434 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/). > For user-friendly release highlights, see the [GitHub Releases](https://github.com/ZhuLinsen/daily_stock_analysis/releases) page. ## [Unreleased] +- [修复] 修复任务状态接口重建报告动作字段时把合法情绪分 `0` 当成空值的问题,确保低分报告能按评分口径纠正为卖出建议。 + diff --git a/tests/test_analysis_api_contract.py b/tests/test_analysis_api_contract.py index fdcc6544f..8dde85ba7 100644 --- a/tests/test_analysis_api_contract.py +++ b/tests/test_analysis_api_contract.py @@ -651,6 +651,114 @@ class AnalysisApiContractTestCase(unittest.TestCase): self.assertEqual(status.result.report["summary"]["action"], "watch") self.assertEqual(status.result.report["summary"]["action_label"], "观望") + def test_get_analysis_status_preserves_zero_sentiment_score_when_aligning_action(self) -> None: + if get_analysis_status is None or analysis_endpoint_module is None: + self.skipTest("analysis endpoint helpers unavailable in this environment") + + created_at = datetime(2026, 5, 21, 17, 40, 0) + queue = MagicMock() + queue.get_task.return_value = SimpleNamespace( + task_id="task-queue-zero-score", + stock_code="600519", + stock_name="贵州茅台", + status=analysis_endpoint_module.TaskStatusEnum.COMPLETED, + progress=100, + result={ + "stock_code": "600519", + "stock_name": "贵州茅台", + "report": { + "meta": { + "query_id": "task-queue-zero-score", + "stock_code": "600519", + "report_type": "detailed", + "report_language": "zh", + }, + "summary": { + "analysis_summary": "趋势显著恶化", + "operation_advice": "持有", + "sentiment_score": 0, + }, + "details": { + "raw_result": { + "operation_advice": "持有", + "report_language": "zh", + }, + }, + }, + }, + error=None, + original_query=None, + selection_source=None, + analysis_phase="auto", + created_at=created_at, + completed_at=datetime(2026, 5, 21, 17, 45, 0), + ) + + with patch("api.v1.endpoints.analysis.get_task_queue", return_value=queue): + status = get_analysis_status("task-queue-zero-score") + + self.assertEqual(status.status, "completed") + self.assertIsNotNone(status.result) + self.assertEqual(status.result.report["summary"]["sentiment_score"], 0) + self.assertEqual(status.result.report["summary"]["action"], "sell") + self.assertEqual(status.result.report["summary"]["action_label"], "卖出") + + def test_get_analysis_status_preserves_zero_sentiment_score_when_enriching_report(self) -> None: + if get_analysis_status is None or analysis_endpoint_module is None: + self.skipTest("analysis endpoint helpers unavailable in this environment") + + created_at = datetime(2026, 5, 21, 17, 40, 0) + queue = MagicMock() + queue.get_task.return_value = SimpleNamespace( + task_id="task-queue-zero-score-enriched", + stock_code="600519", + stock_name="贵州茅台", + status=analysis_endpoint_module.TaskStatusEnum.COMPLETED, + progress=100, + result={ + "stock_code": "600519", + "stock_name": "贵州茅台", + "report": { + "meta": { + "query_id": "task-queue-zero-score-enriched", + "stock_code": "600519", + "report_type": "detailed", + "report_language": "zh", + }, + "summary": { + "analysis_summary": "趋势显著恶化", + "operation_advice": "持有", + "sentiment_score": 0, + }, + "details": { + "raw_result": { + "operation_advice": "持有", + "report_language": "zh", + }, + }, + }, + }, + error=None, + original_query=None, + selection_source=None, + analysis_phase="auto", + created_at=created_at, + completed_at=datetime(2026, 5, 21, 17, 45, 0), + ) + + with patch("api.v1.endpoints.analysis.get_task_queue", return_value=queue), \ + patch( + "api.v1.endpoints.analysis._load_sync_fundamental_sources", + return_value=({}, None), + ): + status = get_analysis_status("task-queue-zero-score-enriched") + + self.assertEqual(status.status, "completed") + self.assertIsNotNone(status.result) + self.assertEqual(status.result.report["summary"]["sentiment_score"], 0) + self.assertEqual(status.result.report["summary"]["action"], "sell") + self.assertEqual(status.result.report["summary"]["action_label"], "卖出") + def test_get_analysis_status_preserves_queue_report_created_at_when_enriching(self) -> None: if get_analysis_status is None or analysis_endpoint_module is None: self.skipTest("analysis endpoint helpers unavailable in this environment")