fix(web): persist C/D rectification answers without waiting for rescore
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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user