fix(rectification): bump scoring identity to scoring-11 after D5/D11 (BUG-1260)

This commit is contained in:
jesse-ux
2026-10-07 17:47:00 +08:00
parent 5eb11e7cd1
commit d46f9675aa
10 changed files with 6978 additions and 33 deletions
@@ -1071,7 +1071,7 @@ export async function runV9Diagnostics(input: {
// Keep the receipt default aligned with Python's ALGORITHM_VERSION; persisted
// receipts retain their original identity (this is not a Skill/open gate).
export const v9EngineVersion = (): string =>
process.env.RECTIFICATION_ENGINE_VERSION?.trim() || "rectification-v5-matrix-scoring-10";
process.env.RECTIFICATION_ENGINE_VERSION?.trim() || "rectification-v5-matrix-scoring-11";
export type V9RangeReading = Readonly<{
stableThemes: readonly string[];
File diff suppressed because it is too large Load Diff
@@ -22,13 +22,14 @@ test("dated contract is a generation threshold, not one literal", () => {
}
});
test("receipt default and Python identity agree on scoring-10", (t) => {
test("receipt default and Python identity agree on scoring-11", (t) => {
const saved = process.env.RECTIFICATION_ENGINE_VERSION;
delete process.env.RECTIFICATION_ENGINE_VERSION;
t.after(() => { if (saved === undefined) delete process.env.RECTIFICATION_ENGINE_VERSION; else process.env.RECTIFICATION_ENGINE_VERSION = saved; });
assert.equal(v9EngineVersion(), "rectification-v5-matrix-scoring-10");
// 原值: rectification-v5-matrix-scoring-10;新值: ...-scoring-11;原因: D5/D11 改变同输入分数(BUG-1260)。
assert.equal(v9EngineVersion(), "rectification-v5-matrix-scoring-11");
const python = readFileSync(new URL("../../scripts/rectification/scoring_service.py", import.meta.url), "utf8");
assert.match(python, /^ALGORITHM_VERSION = "rectification-v5-matrix-scoring-10"$/m);
assert.match(python, /^ALGORITHM_VERSION = "rectification-v5-matrix-scoring-11"$/m);
});
test("no source file compares the algorithm identity with a scoring-N literal", () => {
@@ -73,3 +74,16 @@ test("a scoring-9 history opens read-only under scoring-10 and offers a recompar
// Its candidates still parse as dated (generation >= 9), so the opened history keeps civil dates.
assert.ok(dossier.latestResult!.candidates.every((candidate) => typeof candidate.candidate_date === "string"));
});
test("a scoring-10 history opens read-only under scoring-11 and offers a recompare (BUG-621)", () => {
const historical = JSON.parse(readFileSync(new URL("./fixtures/rectification-midnight-date-anchor.delivery.scoring-10.native.json", import.meta.url), "utf8")).response;
assert.equal(historical.algorithm_version, "rectification-v5-matrix-scoring-10");
const latest = { ...historical, candidates: historical.candidate_decisions };
const dossier = parseV9CaseDossier(dossierFixture({ stage: "minute", latestResult: latest }));
assert.ok(dossier);
assert.equal(dossier.latestResult?.algorithmVersion, "rectification-v5-matrix-scoring-10");
const policyVersion = historical.decision_policy_version;
const underEleven = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-11", policyVersion });
assert.deepEqual([underEleven.read_only, underEleven.can_recompare, underEleven.algorithm_version], [true, true, "rectification-v5-matrix-scoring-10"]);
assert.ok(dossier.latestResult!.candidates.every((candidate) => typeof candidate.candidate_date === "string"));
});
@@ -31,18 +31,21 @@ const golden = JSON.parse(readFileSync(new URL(
// Historical golden identity stays fixed; it must not be relabeled as the live engine.
const CURRENT = "rectification-v5-matrix-scoring-8";
const PREVIOUS = "rectification-v5-matrix-scoring-7";
// 原值: LIVE_CURRENT = "rectification-v5-matrix-scoring-9"(liveGolden 同时充当「当前版本」)
// 新值: LIVE_CURRENT = "...-scoring-10";scoring-9 的 dated golden 留作历史(DATED_HISTORICAL),
// 当前版本另有真实引擎生成的 scoring-10 dated golden(currentGolden)
// 原因: 功能吉凶 v2 改变同输入分数,按「打分语义变化必须 bump」升算法身份(BUG-1181)
const LIVE_CURRENT = "rectification-v5-matrix-scoring-10";
// 原值: LIVE_CURRENT = "rectification-v5-matrix-scoring-10";新值: "...-scoring-11";
// scoring-10 dated golden 留作历史,当前版本另有真实引擎生成的 scoring-11 golden。
// 原因: D5/D11 改变同输入分数(BUG-1260),按 BUG-1181 升算法身份。
const LIVE_CURRENT = "rectification-v5-matrix-scoring-11";
const DATED_HISTORICAL = "rectification-v5-matrix-scoring-9";
const SCORING_10 = "rectification-v5-matrix-scoring-10";
const liveGolden = JSON.parse(readFileSync(new URL(
"./fixtures/rectification-midnight-date-anchor.delivery.native.json", import.meta.url,
), "utf8"));
const currentGolden = JSON.parse(readFileSync(new URL(
const scoring10Golden = JSON.parse(readFileSync(new URL(
"./fixtures/rectification-midnight-date-anchor.delivery.scoring-10.native.json", import.meta.url,
), "utf8"));
const currentGolden = JSON.parse(readFileSync(new URL(
"./fixtures/rectification-midnight-date-anchor.delivery.scoring-11.native.json", import.meta.url,
), "utf8"));
const envKeys = [
"RECTIFICATION_ENGINE_VERSION", "RECTIFICATION_ALGORITHM_VERSION",
"RECTIFICATION_DECISION_POLICY_VERSION",
@@ -69,8 +72,9 @@ test("receipt default matches the cross-midnight native scoring version without
assert.equal(golden.score.algorithm_version, CURRENT);
assert.equal(golden.versions.algorithm_version, CURRENT);
const python = readFileSync(new URL("../../scripts/rectification/scoring_service.py", import.meta.url), "utf8");
assert.match(python, /ALGORITHM_VERSION = "rectification-v5-matrix-scoring-10"/);
assert.match(python, /ALGORITHM_VERSION = "rectification-v5-matrix-scoring-11"/);
assert.equal(liveGolden.response.algorithm_version, DATED_HISTORICAL);
assert.equal(scoring10Golden.response.algorithm_version, SCORING_10);
assert.equal(currentGolden.response.algorithm_version, LIVE_CURRENT);
const fingerprints = { evidenceLedgerFingerprint: "e".repeat(64), candidateRangeFingerprint: "c".repeat(64) };
const live = {
@@ -79,9 +83,17 @@ test("receipt default matches the cross-midnight native scoring version without
};
const historical = { ...fingerprints, algorithmVersion: CURRENT, policyVersion: golden.versions.decision_policy_version };
assert.equal(cachedEngineScoreIsReusable(historical, fingerprints, live), false);
// BUG-1181: a scoring-9 cache (old functional grouping) is not current under scoring-10.
// BUG-1181 / BUG-1260: scoring-9 and scoring-10 caches are not current under scoring-11.
assert.equal(cachedEngineScoreIsReusable({ ...historical, algorithmVersion: DATED_HISTORICAL }, fingerprints, live), false);
assert.equal(cachedEngineScoreIsReusable({ ...historical, algorithmVersion: LIVE_CURRENT }, fingerprints, live), true);
assert.equal(cachedEngineScoreIsReusable({ ...historical, algorithmVersion: SCORING_10 }, fingerprints, live), false);
// 原值: 只把 algorithmVersion 改成 LIVE_CURRENT 即可复用(当时 golden 的策略与旧 fixture 相同)
// 新值: 复用还要带上当前 golden 的 policyVersion
// 原因: scoring-11 回执的策略是 policy-v4,旧跨午夜 fixture 的策略不是这一版(BUG-1260)
assert.equal(cachedEngineScoreIsReusable({
...historical,
algorithmVersion: LIVE_CURRENT,
policyVersion: live.policyVersion,
}, fingerprints, live), true);
assert.equal(historical.algorithmVersion, CURRENT, "checking live identity never relabels history");
// A receipt default must not become an environment override for cache identity.
assert.deepEqual(liveEngineScoringIdentityFromEnv({}), { algorithmVersion: null, policyVersion: null });
@@ -97,9 +97,9 @@ for (const option of ["A", "B", "C", "skip_probe"] as const) test(`D1 initial no
assert.equal(midnightSideChoice(current.case.candidate_range), null);
});
// BUG-1181: the scoring-10 golden (real engine, functional profile v2) must parse as dated too;
// a literal "=== scoring-9" check would have turned it into a legacy same-day result.
for (const fixture of ["rectification-midnight-date-anchor.delivery.native.json", "rectification-midnight-date-anchor.delivery.scoring-10.native.json"]) test(`native golden crosses engine parsing, inference and dated delivery without reanchoring (${fixture})`, async () => {
// BUG-1181: scoring-9 and later goldens must parse as dated.
// 原值: native + scoring-10;新值: 再加 scoring-11;原因: D5/D11 升到 scoring-11(BUG-1260),旧文件仍要按日期窗打开。
for (const fixture of ["rectification-midnight-date-anchor.delivery.native.json", "rectification-midnight-date-anchor.delivery.scoring-10.native.json", "rectification-midnight-date-anchor.delivery.scoring-11.native.json"]) test(`native golden crosses engine parsing, inference and dated delivery without reanchoring (${fixture})`, async () => {
const golden = JSON.parse(readFileSync(new URL(`./fixtures/${fixture}`, import.meta.url), "utf8"));
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () => new Response(JSON.stringify(golden.response), { status: 200 })) as typeof fetch;
+4 -1
View File
@@ -18,7 +18,10 @@ from scripts.rectification.case_holdout import holdout_event_ids
# *_functional_benefic_auxiliary / *_functional_malefic_auxiliary rules, so identical
# input scores differently; cached scoring-9 results must not be reused as
# current. The dated candidate-window contract is unchanged (scoring-9+).
ALGORITHM_VERSION = "rectification-v5-matrix-scoring-10"
# scoring-11 (2026-10-07, BUG-1260): D5/D11 sign mappings change education and
# finance varga points for the same input. Cached scoring-10 results must not
# be reused as current. Policy and the dated window contract stay as they are.
ALGORITHM_VERSION = "rectification-v5-matrix-scoring-11"
INPUT_CONTRACT_VERSION = "rectification-calculation-spec-v5"
PRECISION_WEIGHTS = {
"day": 1.0,
File diff suppressed because it is too large Load Diff
@@ -31,9 +31,9 @@ def test_fixed_scoring_identity_is_exposed_without_changing_input_contract():
ALGORITHM_VERSION, INPUT_CONTRACT_VERSION, calculation_spec, sha256,
)
# 原值: "rectification-v5-matrix-scoring-9";新值: "...-scoring-10";原因: 功能吉凶 v2 改变同输入分数,
# 按「打分语义变化必须 bump」先例升算法身份(BUG-1181);输入合同 v5 与日期窗合同不变。
assert engine_scoring_versions()["algorithm_version"] == ALGORITHM_VERSION == "rectification-v5-matrix-scoring-10"
# 原值: "rectification-v5-matrix-scoring-10";新值: "...-scoring-11";原因: D5/D11 映射改变学业与财务分盘得分
# (BUG-1260),同输入分数变了,按 BUG-1181 升算法身份;输入合同 v5 与日期窗合同不变。
assert engine_scoring_versions()["algorithm_version"] == ALGORITHM_VERSION == "rectification-v5-matrix-scoring-11"
assert INPUT_CONTRACT_VERSION == "rectification-calculation-spec-v5"
request = {"birth_date": "2000-03-01", "start_time": "00:00", "end_time": "00:02",
"lat": 0.0, "lon": 0.0, "tz": 0.0, "events": []}
+36 -11
View File
@@ -30,6 +30,12 @@ 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.
Current golden tests/golden/rectification_engine_memoization_v4.json
(2026-10-07, BUG-1260) was written by ``write_golden`` after the D5/D11
mappings changed education and finance varga points for this same request.
The algorithm is rectification-v5-matrix-scoring-11. Policy stays v4.
v3 stays byte-for-byte and is pinned 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
@@ -68,20 +74,24 @@ import scripts.rectification.scoring_service as scoring_service
import shadbala
ROOT = Path(__file__).resolve().parents[1]
GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v3.json"
GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v4.json"
V3_GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v3.json"
V3_GOLDEN_SHA256 = "7064f30bea3a7bcd8058fdcb6b52484a96a09767c0c289e0d71f3e2368fc8d45"
V3_SOURCE_COMMIT = "7fb165ecf66ba1726f2fc74ffa2353fadc65ba39"
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"
# 原值: rectification-v5-matrix-scoring-10;新值: ...-scoring-11;原因: D5/D11 改变同输入分数(BUG-1260)。
CURRENT_ALGORITHM_VERSION = "rectification-v5-matrix-scoring-11"
FROZEN_TODAY = date(2026, 9, 16)
TIMING_KEYS = frozenset({"column_compare_ms"})
HISTORICAL_SOURCE_COMMIT = "a8d29d1b6cc37ff865ddec6c8bccdf9aa889ee53"
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"
# D5/D11 mapping commit that moves these scores (BUG-1260). The v4 file is
# written later on this branch; v3 keeps its own source commit below.
SOURCE_COMMIT = "db68e90816d462b0c17b4f84a0d19df83010fbce"
CACHE_LAYER_KEYS = (
"ashtakavarga_result",
"shadbala_result",
@@ -265,7 +275,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 / policy-v4 golden; identity is spelled out, not trusted."""
"""Compare with the scoring-11 / 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"}],
@@ -300,7 +310,7 @@ def _assert_dated_payload_against_golden(actual: dict[str, Any], expected: dict[
def test_historical_scoring_8_golden_is_frozen_and_no_longer_current() -> None:
"""BUG-1181: the old golden stays byte-identical; current scores moved, hence scoring-10."""
"""BUG-1181: the scoring-8 golden stays byte-identical and is not the current payload."""
raw = HISTORICAL_GOLDEN_PATH.read_bytes()
assert hashlib.sha256(raw).hexdigest() == HISTORICAL_GOLDEN_SHA256
historical = json.loads(raw)
@@ -313,14 +323,15 @@ def test_historical_scoring_8_golden_is_frozen_and_no_longer_current() -> None:
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."""
"""BUG-1193: v2 stays byte-identical; scores equal v3 (both scoring-10), 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"))
current = json.loads(V3_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
# 原值: 与 CURRENT_ALGORITHM_VERSION 相同;新值: 两者都是 scoring-10;原因: 当前身份已是 scoring-11,v2/v3 仍是当时的 scoring-10。
assert previous["candidate_feature_snapshot"]["algorithm_version"] == "rectification-v5-matrix-scoring-10"
assert current["candidate_feature_snapshot"]["algorithm_version"] == "rectification-v5-matrix-scoring-10"
_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
@@ -332,6 +343,20 @@ def test_previous_policy_v3_golden_is_frozen_with_identical_scores() -> None:
_assert_memoization_payloads(current, previous)
def test_scoring_10_golden_is_frozen_and_scores_moved() -> None:
"""BUG-1260: v3 stays byte-identical; D5/D11 moved this request, hence scoring-11."""
raw = V3_GOLDEN_PATH.read_bytes()
assert hashlib.sha256(raw).hexdigest() == V3_GOLDEN_SHA256
previous = json.loads(raw)
current = json.loads(GOLDEN_PATH.read_text(encoding="utf-8"))
assert previous["source_commit"] == V3_SOURCE_COMMIT
assert previous["candidate_feature_snapshot"]["algorithm_version"] == "rectification-v5-matrix-scoring-10"
assert current["candidate_feature_snapshot"]["algorithm_version"] == CURRENT_ALGORITHM_VERSION
assert current["source_commit"] == SOURCE_COMMIT
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))
+2 -2
View File
@@ -21,8 +21,8 @@ class RelativeSupportScaleTest(unittest.TestCase):
# 原值: 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")
# 原值: -10;新值: -11;原因: D5/D11 改变同输入分数(BUG-1260),策略 v4 与相对支持口径不变。
self.assertEqual(ALGORITHM_VERSION, "rectification-v5-matrix-scoring-11")
self.assertEqual(RELATIVE_SUPPORT_MODE, "proportional")
def test_offset_top_two_lead_is_at_least_proportional(self) -> None: