fix(web): persist C/D rectification answers without waiting for rescore
Independent Staging Quality Gate / validate (push) Failing after 9m53s
Independent Staging Quality Gate / publish (push) Has been skipped

Choice C/D without new evidence never changed the candidate posterior until the next dated-event rescore, and persist-v2 would cache-hit on the same evidence fingerprint. Patch the latest decision_receipt.inference_state in place so the next follow-up sees the asked split immediately.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-23 22:41:01 +08:00
parent 4181dcc5d3
commit 29750d3835
27 changed files with 2082 additions and 21 deletions
+51 -2
View File
@@ -8,6 +8,7 @@ and emit a yes/no life-event question. Never grants a unique minute.
from __future__ import annotations
from datetime import date, datetime, timedelta
from math import log2
from typing import Any, Sequence
from scripts.active_rectification_event_engine import (
@@ -22,6 +23,7 @@ from scripts.rectification.refinement_packet import match_level
MAX_PROBES = 3
LEVEL_RANK = {"none": 0, "weak": 1, "medium": 2, "strong": 3}
LEVEL_P = {"none": 0.15, "weak": 0.35, "medium": 0.62, "strong": 0.82}
SCORING_LAYERS = ("d1", "d9", "d10", "d4", "d5", "d24", "d7", "d12", "d2", "d11", "d30")
LAYER_DOMAIN = {
"d9": "relationship",
@@ -399,6 +401,27 @@ def _agent_brief(
)
def _binary_entropy(probability: float) -> float:
if probability <= 0.0 or probability >= 1.0:
return 0.0
return -(probability * log2(probability) + (1.0 - probability) * log2(1.0 - probability))
def _pair_entropy(left: float, right: float) -> float:
total = left + right
if total <= 0:
return 0.0
return _binary_entropy(left / total)
def _information_gain(left_level: str, right_level: str) -> float:
left_p = LEVEL_P.get(left_level, 0.5)
right_p = LEVEL_P.get(right_level, 0.5)
yes_p = 0.5 * left_p + 0.5 * right_p
after = yes_p * _pair_entropy(left_p, right_p) + (1.0 - yes_p) * _pair_entropy(1.0 - left_p, 1.0 - right_p)
return round(max(0.0, 1.0 - after), 4)
def _public_probe(
*,
year: int,
@@ -408,8 +431,9 @@ def _public_probe(
tracks_agree: bool,
user_meaning: str,
event_family: str,
**extra: Any,
) -> dict[str, Any]:
return {
payload = {
"year": year,
"year_label": _year_label(year),
"domain": domain,
@@ -420,7 +444,13 @@ def _public_probe(
"unique_minute_claim": False,
"user_meaning": user_meaning,
"role": "distinguish" if source == "known_event_quality" else "reverse_verify",
"semantic_key": f"{domain}.{year}",
"information_gain": 0.0,
"candidate_split_hash": f"{domain}:{year}",
"expected_outcomes": [],
}
payload.update(extra)
return payload
QUALITY_HINTS: dict[str, tuple[str, ...]] = {
@@ -509,8 +539,16 @@ def _evaluate_year(
right_rules = scored_right.get("rule_ids") or []
if not _discriminates(left_rules, right_rules):
return None
stronger = left_rules if LEVEL_RANK[match_level(left_rules)] >= LEVEL_RANK[match_level(right_rules)] else right_rules
left_level = match_level(left_rules)
right_level = match_level(right_rules)
stronger = left_rules if LEVEL_RANK[left_level] >= LEVEL_RANK[right_level] else right_rules
vim_hit, narayana_hit = _tracks_present(stronger)
left_time = _context_time(left)
right_time = _context_time(right)
left_stronger = LEVEL_RANK[left_level] >= LEVEL_RANK[right_level]
yes_supports = [time for time in ([left_time] if left_stronger else [right_time]) if time]
yes_conflicts = [time for time in ([right_time] if left_stronger else [left_time]) if time]
split = f"{domain}:{year}:{ '|'.join(sorted(yes_supports + yes_conflicts)) }"
return _public_probe(
year=year,
domain=domain,
@@ -522,6 +560,16 @@ def _evaluate_year(
family=str(DOMAIN_CATALOG[domain]["event_family"]),
),
event_family=str(DOMAIN_CATALOG[domain]["event_family"]),
information_gain=_information_gain(left_level, right_level),
semantic_key=f"{domain}.{year}.{source}",
candidate_split_hash=split,
expected_outcomes=[
{"answer_class": "yes", "supports": yes_supports, "conflicts": yes_conflicts},
{"answer_class": "no", "supports": yes_conflicts, "conflicts": yes_supports},
{"answer_class": "unsure", "supports": [], "conflicts": []},
],
left_time=left_time,
right_time=right_time,
)
@@ -615,6 +663,7 @@ def discriminating_event_probes(
event_family=str(DOMAIN_CATALOG[domain]["event_family"]),
))
covered_domains.add(domain)
probes.sort(key=lambda row: (-float(row.get("information_gain") or 0), str(row.get("semantic_key") or "")))
public: list[dict[str, Any]] = []
seen: set[tuple[str, int, str]] = set()
for row in probes: