fix: preserve zero sentiment score alignment (#1900)

* fix: preserve zero sentiment score alignment

* fix(review-feedback-1900): 修正 docs/CHANGELOG.md 中与本 PR 无关的条目,避免把未包含在当前 diff 的功能/修复写入本 PR and
This commit is contained in:
zhulinsen
2026-07-04 09:23:18 +08:00
committed by GitHub
parent 77911efbc5
commit 282efa18e1
3 changed files with 128 additions and 5 deletions

View File

@@ -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,

View File

@@ -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` 当成空值的问题,确保低分报告能按评分口径纠正为卖出建议。
<!-- 新条目格式:- [类型] 描述(类型取值:新功能/改进/修复/文档/测试/chore-->
<!-- 每条独立一行追加到本段末尾,无需分类标题,合并时冲突最小 -->

View File

@@ -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")