fix: avoid claiming deterministic screening rank

This commit is contained in:
ZhuLinsen
2026-08-30 21:10:34 +08:00
parent 1c194d332e
commit bffc793595
3 changed files with 10 additions and 10 deletions

View File

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

View File

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

View File

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