fix(web): count only training events for discrimination and split user-stop from validated range

Three collected events with a reserved holdout were stalling because the discriminator door counted holdout. Public selection_allowed still had snapshot fallbacks, and health only proved the image SHA.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-26 19:36:04 +08:00
parent a0ce55f066
commit b1173f7245
37 changed files with 1362 additions and 234 deletions
+15 -1
View File
@@ -77,8 +77,22 @@ def scoreable_event_stats(events: Sequence[dict[str, Any]] | None) -> tuple[int,
return len(scoreable), len(domains), domains
def training_scoreable_stats(events: Sequence[dict[str, Any]] | None) -> tuple[int, int, frozenset[str]]:
from scripts.rectification.case_holdout import holdout_event_ids
holdout = holdout_event_ids(events)
training = [
event for event in (events or [])
if isinstance(event, dict)
and is_primary_scoreable_dict(event)
and str(event.get("id") or "") not in holdout
]
domains = frozenset(str(event["domain"]) for event in training)
return len(training), len(domains), domains
def discriminator_gate_open(events: Sequence[dict[str, Any]] | None) -> bool:
count, domain_count, _ = scoreable_event_stats(events)
count, domain_count, _ = training_scoreable_stats(events)
return count >= MIN_DISCRIMINATOR_EVENTS and domain_count >= MIN_DISCRIMINATOR_DOMAINS
+10 -4
View File
@@ -16,6 +16,7 @@ from scripts.rectification.house_table import compact_house_table_from_contexts
from scripts.rectification.horary_observation import build_horary_observation
from scripts.rectification.refinement_packet import build_refinement_packet
from scripts.rectification.candidate_contrast import context_time, select_signature_representatives
from scripts.rectification.case_holdout import holdout_event_ids
from scripts.rectification.scoring_service import precision_weight
from scripts.rectification.sealed_holdout import holdout_passed, load_sealed_minute_holdout
from scripts.rectification_policy import (
@@ -424,11 +425,16 @@ def build_decision_receipt(
diagnostics: dict[str, Any],
) -> dict[str, Any]:
scoreable_events = [event for event in request["events"] if is_primary_scoreable_event(event)]
domains = sorted({event["domain"] for event in scoreable_events})
holdout = holdout_event_ids(request["events"])
training_events = [
event for event in scoreable_events
if str(event.get("id") or "") not in holdout
]
domains = sorted({event["domain"] for event in training_events})
candidate_presence = _gate(bool(candidate_decisions), candidate_count=len(candidate_decisions))
event_quality = _gate(
len(scoreable_events) >= MIN_ACCEPTANCE_EVENTS,
scoreable_event_count=len(scoreable_events),
len(training_events) >= MIN_ACCEPTANCE_EVENTS,
scoreable_event_count=len(training_events),
minimum=MIN_ACCEPTANCE_EVENTS,
)
domain_diversity = _gate(
@@ -437,7 +443,7 @@ def build_decision_receipt(
minimum=MIN_ACCEPTANCE_DOMAINS,
domains=domains,
)
date_quality = _date_quality(scoreable_events)
date_quality = _date_quality(training_events)
top_tied_count = candidate_decisions[0]["tied_minute_count"] if candidate_decisions else 0
unique_top = _gate(top_tied_count == 1, tied_minute_count=top_tied_count)
diagnostic_quality = _diagnostic_quality(diagnostics)
+3 -1
View File
@@ -307,7 +307,9 @@ def score_from_matrix(request: RectificationRequest, built: dict[str, Any]) -> l
continue
if event["id"] in holdout:
continue
contribution = built["matrix"][event["id"]][candidate_time]
contribution = (built.get("matrix") or {}).get(event["id"], {}).get(candidate_time)
if not isinstance(contribution, dict):
continue
evidence.append({
"event_id": event["id"], "domain": event["domain"], "candidate_time": candidate_time,
"rule_ids": contribution["rule_ids"], "points": contribution["points"],