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
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user