mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
fix: avoid claiming deterministic screening rank
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
|
||||
## Why Selected
|
||||
|
||||
确定性本地解释优先使用 screening reason 和当前策略实际参与评分的因子;零权重或未配置的因子既不会进入缺省 `selection_reason`,也不会被写成“核心因子”,两处展示顺序都按“因子分数 × 策略权重”的真实贡献排列。`risk_summary` / `risk_level` 始终保留在独立风险展示,不会在缺少 reason 时提升为 `selection_reason`;行业标签也不会单独冒充入选依据。缺少 reason 时只回退到加权因子或确定性排名说明。来自 `post_analysis_summaries` 的 DSA/外部 analyzer 摘要保留 `post_analyzer:<name>` 来源并标记为 inferred,不冒充本地 observed;纯本地确定性 `scorecard` 摘要保持 observed,但只要 scorecard 消费了 `llm_confidence`、`llm_catalysts` 或 `llm_risks`,其解释质量就保持 inferred。即使 LLM 未配置、超时或返回无效结构,候选仍至少返回确定性排序/入选说明;LLM 不是本地解释的前置条件。
|
||||
确定性本地解释优先使用 screening reason 和当前策略实际参与评分的因子;零权重或未配置的因子既不会进入缺省 `selection_reason`,也不会被写成“核心因子”,两处展示顺序都按“因子分数 × 策略权重”的真实贡献排列。`risk_summary` / `risk_level` 始终保留在独立风险展示,不会在缺少 reason 时提升为 `selection_reason`;行业标签也不会单独冒充入选依据。缺少 reason 和可核验加权因子时只确认“已进入当前选股候选结果”,不会把可能经过 LLM 排序、组合约束或后处理调整的最终名次误写成“确定性筛选排名”。来自 `post_analysis_summaries` 的 DSA/外部 analyzer 摘要保留 `post_analyzer:<name>` 来源并标记为 inferred,不冒充本地 observed;纯本地确定性 `scorecard` 摘要保持 observed,但只要 scorecard 消费了 `llm_confidence`、`llm_catalysts` 或 `llm_risks`,其解释质量就保持 inferred。即使 LLM 未配置、超时或返回无效结构,候选仍至少返回入选结果说明;LLM 不是本地解释的前置条件。
|
||||
|
||||
## Why Now
|
||||
|
||||
|
||||
@@ -3918,11 +3918,10 @@ def _attach_candidate_explanations(
|
||||
)
|
||||
|
||||
if not any(item.get("quality") == "observed" for item in why_selected):
|
||||
rank = candidate.get("rank")
|
||||
why_selected.append(
|
||||
_explanation_item(
|
||||
"selection_rank",
|
||||
f"通过确定性筛选并排在第 {rank} 位" if rank is not None else "通过确定性筛选",
|
||||
"selection_outcome",
|
||||
"已进入当前选股候选结果",
|
||||
source="screening",
|
||||
quality="observed",
|
||||
)
|
||||
|
||||
@@ -177,7 +177,8 @@ def test_llm_reason_does_not_replace_the_observed_local_selection_fallback() ->
|
||||
result = _attach_candidate_explanations(candidate)
|
||||
|
||||
assert [item["quality"] for item in result["why_selected"]] == ["inferred", "observed"]
|
||||
assert result["why_selected"][1]["code"] == "selection_rank"
|
||||
assert result["why_selected"][1]["code"] == "selection_outcome"
|
||||
assert result["why_selected"][1]["text"] == "已进入当前选股候选结果"
|
||||
assert result["explanation_quality"]["why_selected"] == "partial"
|
||||
|
||||
|
||||
@@ -196,7 +197,7 @@ def test_distinct_llm_ranking_reason_stays_inferred_and_keeps_rank_fallback() ->
|
||||
result = _attach_candidate_explanations(candidate)
|
||||
|
||||
assert [item["quality"] for item in result["why_selected"]] == ["inferred", "observed"]
|
||||
assert result["why_selected"][1]["code"] == "selection_rank"
|
||||
assert result["why_selected"][1]["code"] == "selection_outcome"
|
||||
|
||||
|
||||
def test_news_and_events_without_provenance_are_not_observed() -> None:
|
||||
@@ -229,7 +230,7 @@ def test_llm_risk_summary_stays_inferred_and_keeps_rank_fallback() -> None:
|
||||
result = _attach_candidate_explanations(candidate)
|
||||
|
||||
assert [item["quality"] for item in result["why_selected"]] == ["inferred", "observed"]
|
||||
assert result["why_selected"][1]["code"] == "selection_rank"
|
||||
assert result["why_selected"][1]["code"] == "selection_outcome"
|
||||
|
||||
|
||||
def test_risk_summary_is_not_promoted_to_selection_reason_when_reason_is_missing() -> None:
|
||||
@@ -243,7 +244,7 @@ def test_risk_summary_is_not_promoted_to_selection_reason_when_reason_is_missing
|
||||
|
||||
assert candidate["risk_summary"] == "估值过高"
|
||||
assert candidate["reason"] == ""
|
||||
assert [item["code"] for item in result["why_selected"]] == ["selection_rank"]
|
||||
assert [item["code"] for item in result["why_selected"]] == ["selection_outcome"]
|
||||
assert all("估值过高" not in item["text"] for item in result["why_selected"])
|
||||
|
||||
|
||||
@@ -260,7 +261,7 @@ def test_post_analyzer_summary_keeps_inferred_provenance() -> None:
|
||||
assert reason["code"] == "selection_reason"
|
||||
assert reason["source"] == "post_analyzer:dsa"
|
||||
assert reason["quality"] == "inferred"
|
||||
assert result["why_selected"][1]["code"] == "selection_rank"
|
||||
assert result["why_selected"][1]["code"] == "selection_outcome"
|
||||
|
||||
|
||||
def test_local_scorecard_summary_keeps_observed_provenance() -> None:
|
||||
@@ -306,7 +307,7 @@ def test_risk_level_is_not_promoted_to_selection_reason() -> None:
|
||||
result = _attach_candidate_explanations(candidate)
|
||||
|
||||
assert candidate["reason"] == ""
|
||||
assert [item["code"] for item in result["why_selected"]] == ["selection_rank"]
|
||||
assert [item["code"] for item in result["why_selected"]] == ["selection_outcome"]
|
||||
assert "风险" not in result["why_selected"][0]["text"]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user