Files
Jyotisha/scripts/rectification/probe_question_contract.py
T
Jesse_Chen 7f82428b44
Independent Staging Quality Gate / validate (push) Failing after 11m51s
Independent Staging Quality Gate / publish (push) Has been skipped
fix(rectification): unify discriminator question contract and rank by information gain
Python and TypeScript now share a four-option probe contract, persist Focus before asking, and pick the highest-value renderable probe instead of preferring low-gain career events over D24.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 00:25:35 +08:00

116 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Shared probe → choice-card contract for Python event probes.
Must stay aligned with frontend/src/lib/rectification-agentic/v9/probe-question-contract.ts.
"""
from __future__ import annotations
import re
from typing import Any, Sequence
QUESTION_CONTRACT_VERSION = "probe-question-v1"
ANSWER_CLASSES = ("yes", "weak_yes", "no", "unsure")
EXISTENCE_STYLE_OPTIONS: tuple[dict[str, str], ...] = (
{"label": "明确发生且时间吻合", "answer_class": "yes"},
{"label": "发生过但程度较弱", "answer_class": "weak_yes"},
{"label": "明确没有发生", "answer_class": "no"},
{"label": "这段记不清楚", "answer_class": "unsure"},
)
QUALITY_STYLE_OPTIONS: tuple[dict[str, str], ...] = (
{"label": "发挥明显失常或压力很大", "answer_class": "yes"},
{"label": "有压力但不算明显失常", "answer_class": "weak_yes"},
{"label": "发挥正常、没有明显失常", "answer_class": "no"},
{"label": "这段记不清楚", "answer_class": "unsure"},
)
VARGA_NONE_STYLE_OPTION = {"label": "都不是这些特质", "answer_class": "no"}
UNSURE_STYLE_OPTION = {"label": "这段记不清楚", "answer_class": "unsure"}
_FORBIDDEN = ("外貌", "体质", "胎记", "疤痕", "伤疤", "身高", "体型")
_CLOCK = re.compile(r"(?:[01]?\d|2[0-3]):[0-5]\d")
def probe_question_kind(value: Any) -> str:
if value in {"varga_style", "event_quality"}:
return str(value)
return "existence"
def clipped_probe_label(value: Any, minimum: int = 4, maximum: int = 80) -> str | None:
if not isinstance(value, str):
return None
text = " ".join(value.split())
if len(text) < minimum or len(text) > maximum:
return None
if any(token in text for token in _FORBIDDEN) or _CLOCK.search(text):
return None
return text
def _incoming_option(row: Any) -> dict[str, str] | None:
if not isinstance(row, dict):
return None
answer = row.get("answer_class") or row.get("answerClass")
label = clipped_probe_label(row.get("label"))
if not label or answer not in ANSWER_CLASSES:
return None
payload = {"label": label, "answer_class": str(answer)}
sign = row.get("sign")
if isinstance(sign, str) and sign.strip():
payload["sign"] = sign.strip()
return payload
def complete_style_options(
choice_kind: Any,
style_options: Sequence[Any] | None = None,
) -> list[dict[str, str]] | None:
kind = probe_question_kind(choice_kind)
incoming = [item for item in (_incoming_option(row) for row in (style_options or [])) if item]
by_class: dict[str, dict[str, str]] = {}
if kind == "varga_style":
for option in incoming:
by_class[option["answer_class"]] = option
by_class.setdefault("unsure", dict(UNSURE_STYLE_OPTION))
scoring = [item for item in ANSWER_CLASSES if item != "unsure" and item in by_class]
if len(scoring) < 2:
return None
by_class.setdefault("no", dict(VARGA_NONE_STYLE_OPTION))
if "yes" not in by_class or "weak_yes" not in by_class:
return None
else:
catalog = QUALITY_STYLE_OPTIONS if kind == "event_quality" else EXISTENCE_STYLE_OPTIONS
for option in catalog:
by_class[option["answer_class"]] = dict(option)
for option in incoming:
by_class[option["answer_class"]] = option
ordered: list[dict[str, str]] = []
seen: set[str] = set()
for answer_class in ANSWER_CLASSES:
option = by_class.get(answer_class)
if not option:
return None
label = option["label"]
if label in seen and option.get("sign"):
label = f"{label}{option['sign']}"
if label in seen:
label = f"{label}·{answer_class}"
seen.add(label)
ordered.append({**option, "label": label})
labels = {item["label"] for item in ordered}
classes = {item["answer_class"] for item in ordered}
if len(ordered) != 4 or labels != {item["label"] for item in ordered} or classes != set(ANSWER_CLASSES):
return None
if len(labels) != 4:
return None
return ordered
def is_renderable_probe(probe: dict[str, Any]) -> bool:
gain = probe.get("information_gain")
if not isinstance(gain, (int, float)) or gain <= 0:
return False
candidate_ids = probe.get("candidate_ids") or []
outcomes = probe.get("expected_outcomes") or []
if len(candidate_ids) < 2 or len(outcomes) < 2:
return False
return complete_style_options(probe.get("choice_kind"), probe.get("style_options")) is not None