feat(rectification): range delivery needs 4 dated events across 3 domains (R1, BUG-1193)
Product decision 2026-10-02 (TASK-upstream-sync5 R1): a time range is offered
only with at least 4 dated, primary-scoreable events covering 3 domains,
counted on all of them (training + reserved holdout). Was 3 training events /
2 domains in three TS copies and the Python acceptance gate while the policy
file already said 4/3.
- One definition: references/rectification_policy.v1.json
(minConfirmationEvents / minConfirmationDomains). TS core/types MIN_DATED_*,
rectification-decision MIN_STANDALONE_*, evidence-model MIN_ACCEPTANCE_*,
the convergence evaluator and the post-inference trainingGateOpen all read
it; Python decision_policy MIN_ACCEPTANCE_* alias MIN_CONFIRMATION_*.
- Python receipt counts all scoreable events / domains for event_quality and
domain_diversity; decision policy identity v3 -> v4 (candidate UUIDs carry
it). Candidate scores unchanged (77 v5 cases A/B identical), so the
algorithm stays rectification-v5-matrix-scoring-10.
- Memoization golden v3 written by write_golden; v2 frozen by sha256 with a
test that its scores equal v3 and only the receipt policy moved.
- Collect gap copy names the exact gap ("再来两件……其中至少一件不是……")
instead of always "再来一件"; VOICE.md updated. Legacy life-events form copy
4/3 as well.
- 30 frontend test files, 4 Python tests: fixtures extended to the same
scenario at 4/3, or assertions changed with 原值/新值/原因 notes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7fb165ecf6
commit
758fee954b
File diff suppressed because it is too large
Load Diff
@@ -226,7 +226,7 @@ def test_kp_only_missing_does_not_block_propose_or_engine_grant() -> None:
|
||||
assert "missing_mandatory_layers" not in receipt["confirmation_reasons"]
|
||||
|
||||
|
||||
def test_four_events_two_domains_selects_but_does_not_propose() -> None:
|
||||
def test_four_events_two_domains_neither_selects_nor_proposes() -> None:
|
||||
events = [
|
||||
{
|
||||
"id": "00000000-0000-4000-8000-000000000001",
|
||||
@@ -277,7 +277,14 @@ def test_four_events_two_domains_selects_but_does_not_propose() -> None:
|
||||
"primary_secondary_margin_percent": 10.27,
|
||||
},
|
||||
)
|
||||
assert receipt["selection_allowed"] is True
|
||||
# 原值: selection_allowed=True(区间门槛 3 件 2 域,4 件 2 域可选不可提议)
|
||||
# 新值: selection_allowed=False,domain_diversity 不通过,原因 insufficient_domain_diversity
|
||||
# 原因: R1 生时校正交付门槛 4 件 3 域(BUG-1193),2 个领域不再给区间
|
||||
assert receipt["selection_allowed"] is False
|
||||
assert receipt["gates"]["event_quality"]["passed"] is True
|
||||
assert receipt["gates"]["domain_diversity"]["passed"] is False
|
||||
assert receipt["gates"]["domain_diversity"]["minimum"] == 3
|
||||
assert "insufficient_domain_diversity" in receipt["reasons"]
|
||||
assert receipt["propose_allowed"] is False
|
||||
assert receipt["gates"]["exact_confirmation"]["engine_granted"] is False
|
||||
assert "insufficient_confirmation_domains" in receipt["confirmation_reasons"]
|
||||
|
||||
@@ -20,6 +20,16 @@ input (candidate 12:00: 8.6274 -> 8.4977), which is why the algorithm identity
|
||||
was bumped; it already carries the dated-v1 receipt metadata. Never rewrite it
|
||||
in place: a future scoring change adds a v3 file and freezes this one.
|
||||
|
||||
Current golden tests/golden/rectification_engine_memoization_v3.json
|
||||
(2026-10-02, BUG-1193, upstream sync 5) was written by ``write_golden`` after
|
||||
R1 raised the range floor to 4 dated events / 3 domains. Candidate scores are
|
||||
identical to v2 (the ported combustion / Chara Karaka / Yogini / Shodhana
|
||||
changes do not feed the event matrix; 77 v5 cases A/B byte-identical), so the
|
||||
algorithm stays scoring-10. Only the decision receipt moved: policy
|
||||
rectification-candidate-policy-v3 -> v4, event_quality / domain_diversity
|
||||
minimum 3/2 -> 4/3, and the representative candidate UUID (its namespace
|
||||
carries the policy version). v2 is frozen by sha256 below.
|
||||
|
||||
Do not compare that payload with a whole-structure ``==``. Cross-machine
|
||||
libm / pyswisseph rounding already drifted ``margin_percent`` by 1.1e-3
|
||||
(TASK-rectification-engine-memoization-fix-20260915). Equivalence of the
|
||||
@@ -58,15 +68,20 @@ import scripts.rectification.scoring_service as scoring_service
|
||||
import shadbala
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v2.json"
|
||||
GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v3.json"
|
||||
PREVIOUS_GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v2.json"
|
||||
PREVIOUS_GOLDEN_SHA256 = "abb15efae4d86387d930cd463b82a22fe95ccd1779e85ca6cc4cdf07a063cb73"
|
||||
PREVIOUS_SOURCE_COMMIT = "57782aea8ae28f5dd165a08d221ec27dbef391f8"
|
||||
HISTORICAL_GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v1.json"
|
||||
HISTORICAL_GOLDEN_SHA256 = "6266e448d7dad204b264cbe8f9bcf4c36e02286794764a94edb9e2ce4d0e3015"
|
||||
CURRENT_ALGORITHM_VERSION = "rectification-v5-matrix-scoring-10"
|
||||
FROZEN_TODAY = date(2026, 9, 16)
|
||||
TIMING_KEYS = frozenset({"column_compare_ms"})
|
||||
HISTORICAL_SOURCE_COMMIT = "a8d29d1b6cc37ff865ddec6c8bccdf9aa889ee53"
|
||||
# Engine code of the v2 golden; the scoring-10 label is the BUG-1181 commit on top.
|
||||
SOURCE_COMMIT = "57782aea8ae28f5dd165a08d221ec27dbef391f8"
|
||||
CURRENT_POLICY_VERSION = "rectification-candidate-policy-v4"
|
||||
# Engine code of the v3 golden: upstream-sync5 tip before R1; the policy-v4
|
||||
# receipt (R1, BUG-1193) is the commit that adds the v3 file on top.
|
||||
SOURCE_COMMIT = "7fb165ecf66ba1726f2fc74ffa2353fadc65ba39"
|
||||
CACHE_LAYER_KEYS = (
|
||||
"ashtakavarga_result",
|
||||
"shadbala_result",
|
||||
@@ -250,7 +265,7 @@ def _contexts_with_layer_cache_cleared(contexts: list[dict[str, Any]]) -> list[d
|
||||
|
||||
|
||||
def _assert_dated_payload_against_golden(actual: dict[str, Any], expected: dict[str, Any]) -> None:
|
||||
"""Compare with the scoring-10 golden; identity is spelled out, not trusted."""
|
||||
"""Compare with the scoring-10 / policy-v4 golden; identity is spelled out, not trusted."""
|
||||
metadata = {
|
||||
"candidate_window_contract": "dated-v1",
|
||||
"candidate_intervals": [{"start_at": "1990-01-01T12:00", "end_at": "1990-01-01T12:02"}],
|
||||
@@ -276,7 +291,7 @@ def _assert_dated_payload_against_golden(actual: dict[str, Any], expected: dict[
|
||||
).encode()).hexdigest()
|
||||
result_id = uuid5(NAMESPACE_URL, f"{CURRENT_ALGORITHM_VERSION}:{fingerprint}")
|
||||
representative_time = golden_receipt["representative_time"]
|
||||
candidate_id = uuid5(NAMESPACE_URL, f"rectification-candidate-policy-v3:{result_id}:{representative_time}")
|
||||
candidate_id = uuid5(NAMESPACE_URL, f"{CURRENT_POLICY_VERSION}:{result_id}:{representative_time}")
|
||||
assert golden_receipt["representative_candidate_id"] == str(candidate_id)
|
||||
assert receipt["representative_candidate_id"] == str(candidate_id)
|
||||
# BUG-733/985: fingerprints hash unrounded floats; do not turn the
|
||||
@@ -297,6 +312,26 @@ def test_historical_scoring_8_golden_is_frozen_and_no_longer_current() -> None:
|
||||
_assert_memoization_payloads(current, historical)
|
||||
|
||||
|
||||
def test_previous_policy_v3_golden_is_frozen_with_identical_scores() -> None:
|
||||
"""BUG-1193: v2 stays byte-identical; scores equal v3 (no scoring-11), only the receipt policy moved."""
|
||||
raw = PREVIOUS_GOLDEN_PATH.read_bytes()
|
||||
assert hashlib.sha256(raw).hexdigest() == PREVIOUS_GOLDEN_SHA256
|
||||
previous = json.loads(raw)
|
||||
current = json.loads(GOLDEN_PATH.read_text(encoding="utf-8"))
|
||||
assert previous["source_commit"] == PREVIOUS_SOURCE_COMMIT
|
||||
assert previous["candidate_feature_snapshot"]["algorithm_version"] == CURRENT_ALGORITHM_VERSION
|
||||
assert current["candidate_feature_snapshot"]["algorithm_version"] == CURRENT_ALGORITHM_VERSION
|
||||
_assert_tiered_equal(current["candidate_scores"], previous["candidate_scores"], path="candidate_scores")
|
||||
assert previous["decision_receipt"]["policy_version"] == "rectification-candidate-policy-v3"
|
||||
assert current["decision_receipt"]["policy_version"] == CURRENT_POLICY_VERSION
|
||||
assert previous["decision_receipt"]["gates"]["event_quality"]["minimum"] == 3
|
||||
assert current["decision_receipt"]["gates"]["event_quality"]["minimum"] == 4
|
||||
assert previous["decision_receipt"]["gates"]["domain_diversity"]["minimum"] == 2
|
||||
assert current["decision_receipt"]["gates"]["domain_diversity"]["minimum"] == 3
|
||||
with pytest.raises(AssertionError):
|
||||
_assert_memoization_payloads(current, previous)
|
||||
|
||||
|
||||
def test_score_candidates_matches_baseline_golden() -> None:
|
||||
expected = json.loads(GOLDEN_PATH.read_text(encoding="utf-8"))
|
||||
actual = json.loads(json.dumps(_golden_payload(), ensure_ascii=True))
|
||||
|
||||
@@ -18,7 +18,8 @@ def _row(time: str, score: float) -> dict:
|
||||
|
||||
class RelativeSupportScaleTest(unittest.TestCase):
|
||||
def test_holdout_gate_keeps_proportional_default(self) -> None:
|
||||
self.assertEqual(POLICY_VERSION, "rectification-candidate-policy-v3")
|
||||
# 原值: policy-v3;新值: policy-v4;原因: R1 区间门槛 4 件 3 域(BUG-1193),相对支持口径不变。
|
||||
self.assertEqual(POLICY_VERSION, "rectification-candidate-policy-v4")
|
||||
# Explicit date windows: scoring identity -8 -> -9, not a prior/policy change.
|
||||
# 原值: -9;新值: -10;原因: 功能吉凶 v2 改变打分(BUG-1181),策略 v3 与相对支持口径不变。
|
||||
self.assertEqual(ALGORITHM_VERSION, "rectification-v5-matrix-scoring-10")
|
||||
|
||||
@@ -503,7 +503,8 @@ class RectificationV5ServicesTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(first["decision_receipt"], receipt)
|
||||
# C2 holdout 校准:offset/softmax 覆盖下降,默认仍用 proportional,policy 不 bump
|
||||
self.assertEqual(first["decision_policy_version"], "rectification-candidate-policy-v3")
|
||||
# 原值: policy-v3;新值: policy-v4;原因: R1 区间门槛 3 件 2 域 → 4 件 3 域(BUG-1193),policy 身份随门槛 bump
|
||||
self.assertEqual(first["decision_policy_version"], "rectification-candidate-policy-v4")
|
||||
self.assertTrue(receipt["display_allowed"])
|
||||
self.assertTrue(receipt["accept_allowed"])
|
||||
self.assertFalse(receipt["confirm_allowed"])
|
||||
|
||||
Reference in New Issue
Block a user