fix: make conversational rectification converge

This commit is contained in:
Jesse_Chen
2026-07-25 11:46:08 +08:00
parent 58579cade4
commit 0e6ed6ba79
23 changed files with 1257 additions and 180 deletions
+19 -4
View File
@@ -12,6 +12,21 @@ from collections.abc import Sequence
from typing import Any, Final, Literal, NotRequired, TypedDict, assert_never
from uuid import NAMESPACE_URL, uuid5
try:
from scripts.rectification_policy import (
MAX_CONFIRMATION_WIDTH_MINUTES,
MIN_CONFIRMATION_DOMAINS,
MIN_CONFIRMATION_EVENTS,
MIN_CONFIRMATION_MARGIN_PERCENT,
)
except ModuleNotFoundError: # pragma: no cover - direct script execution
from rectification_policy import (
MAX_CONFIRMATION_WIDTH_MINUTES,
MIN_CONFIRMATION_DOMAINS,
MIN_CONFIRMATION_EVENTS,
MIN_CONFIRMATION_MARGIN_PERCENT,
)
EventPrecision = Literal["year", "month", "day"]
EventDomain = Literal[
"education",
@@ -253,11 +268,11 @@ def adjudicate_candidate_rows(
if reasons:
confidence: Confidence = "low"
elif (
event_count >= 4
and domain_count >= 3
event_count >= MIN_CONFIRMATION_EVENTS
and domain_count >= MIN_CONFIRMATION_DOMAINS
and segment is not None
and segment["width_minutes"] <= 5
and margin >= 20
and segment["width_minutes"] <= MAX_CONFIRMATION_WIDTH_MINUTES
and margin >= MIN_CONFIRMATION_MARGIN_PERCENT
):
confidence = "high"
else:
+48 -7
View File
@@ -41,9 +41,25 @@ if SCRIPTS_DIR not in sys.path:
try:
from scripts.local_env import load_local_env
from scripts.rectification_policy import (
MAX_CONFIRMATION_WIDTH_MINUTES,
MAX_EXTERNAL_VALIDATION_WIDTH_MINUTES,
MIN_CONFIRMATION_DOMAINS,
MIN_CONFIRMATION_EVENTS,
MIN_CONFIRMATION_MARGIN_PERCENT,
MIN_SCORING_EVENTS,
)
from scripts.vedastro_runtime_context import temporary_timeout_seconds
except ModuleNotFoundError: # pragma: no cover - script execution path
from local_env import load_local_env
from rectification_policy import (
MAX_CONFIRMATION_WIDTH_MINUTES,
MAX_EXTERNAL_VALIDATION_WIDTH_MINUTES,
MIN_CONFIRMATION_DOMAINS,
MIN_CONFIRMATION_EVENTS,
MIN_CONFIRMATION_MARGIN_PERCENT,
MIN_SCORING_EVENTS,
)
from vedastro_runtime_context import temporary_timeout_seconds
try:
from scripts.unified_consultation_orchestrator import UnifiedConsultationOrchestrator
@@ -321,15 +337,39 @@ def _rectification_candidate_ready_for_external_validation(result):
except (TypeError, ValueError):
return False
return (
int(result.get('event_count') or 0) >= 3
and int(result.get('domain_count') or 0) >= 2
and 1 <= width_minutes <= 15
int(result.get('event_count') or 0) >= MIN_CONFIRMATION_EVENTS
and int(result.get('domain_count') or 0) >= MIN_CONFIRMATION_DOMAINS
and 1 <= width_minutes <= MAX_EXTERNAL_VALIDATION_WIDTH_MINUTES
and bool(segment.get('representative_time'))
and top_score > second_score
and 'missing_mandatory_layers' not in (result.get('reasons') or [])
)
def _rectification_candidate_ready_for_confirmation(result):
"""Keep final confirmation stricter than the external-validation entry gate."""
segment = result.get('winning_segment')
ranking = result.get('candidate_ranking_summary')
if not isinstance(segment, dict) or not isinstance(ranking, list) or len(ranking) < 2:
return False
try:
width_minutes = int(segment.get('width_minutes') or 0)
top_score = float(result.get('top_score'))
second_score = float(result.get('second_score'))
margin_percent = float(result.get('margin_percent') or 0)
except (TypeError, ValueError):
return False
return (
int(result.get('event_count') or 0) >= MIN_CONFIRMATION_EVENTS
and int(result.get('domain_count') or 0) >= MIN_CONFIRMATION_DOMAINS
and 1 <= width_minutes <= MAX_CONFIRMATION_WIDTH_MINUTES
and bool(segment.get('representative_time'))
and top_score > second_score
and margin_percent >= MIN_CONFIRMATION_MARGIN_PERCENT
and 'missing_mandatory_layers' not in (result.get('reasons') or [])
)
def build_evidence_packet_view(job_record: dict | None) -> dict:
"""Public, token-protected job view. Excludes prompt internals and raw input."""
job_record = job_record or {}
@@ -7183,8 +7223,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
lon = self._get_float(body, 'lon', 0, -180, 180)
tz = self._get_float(body, 'tz', 0, -14, 14)
events = body.get('events')
if not isinstance(events, list) or len(events) < 3:
raise BadRequest('events must contain at least 3 items')
if not isinstance(events, list) or len(events) < MIN_SCORING_EVENTS:
raise BadRequest(f'events must contain at least {MIN_SCORING_EVENTS} item')
normalized_events = []
allowed_domains = {'education', 'relocation', 'relationship', 'career', 'finance', 'health_pressure'}
formats = {'year': '%Y', 'month': '%Y-%m', 'day': '%Y-%m-%d'}
@@ -7238,8 +7278,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
'vedastro_reason': 'official_vedastro_runs_after_local_scoring_produces_a_narrow_candidate',
'blockers': [],
}
local_candidate_ready = _rectification_candidate_ready_for_external_validation(result)
if high_rigor and local_candidate_ready:
external_validation_candidate_ready = _rectification_candidate_ready_for_external_validation(result)
if high_rigor and external_validation_candidate_ready:
from scripts.rectification_three_engine_packet import build_packet
representative_time = result['winning_segment']['representative_time']
@@ -7430,6 +7470,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
}
from scripts.rectification_technique_contract import build_rectification_technique_contract
local_candidate_ready = _rectification_candidate_ready_for_confirmation(result)
result['technique_contract'] = build_rectification_technique_contract(
event_count=result.get('event_count', 0),
domain_count=result.get('domain_count', 0),
+18
View File
@@ -0,0 +1,18 @@
"""Shared birth-time rectification convergence policy."""
from __future__ import annotations
import json
from pathlib import Path
from typing import Final
_POLICY_PATH = Path(__file__).resolve().parents[1] / "references" / "rectification_policy.v1.json"
POLICY: Final[dict[str, int | str]] = json.loads(_POLICY_PATH.read_text(encoding="utf-8"))
MIN_SCORING_EVENTS: Final = int(POLICY["minScoringEvents"])
MIN_CONFIRMATION_EVENTS: Final = int(POLICY["minConfirmationEvents"])
MIN_CONFIRMATION_DOMAINS: Final = int(POLICY["minConfirmationDomains"])
MAX_EXTERNAL_VALIDATION_WIDTH_MINUTES: Final = int(POLICY["maxExternalValidationWidthMinutes"])
MAX_CONFIRMATION_WIDTH_MINUTES: Final = int(POLICY["maxConfirmationWidthMinutes"])
MIN_CONFIRMATION_MARGIN_PERCENT: Final = int(POLICY["minConfirmationMarginPercent"])
MAX_PLATEAU_ROUNDS: Final = int(POLICY["maxPlateauRounds"])
+15 -7
View File
@@ -3,6 +3,14 @@ from __future__ import annotations
from typing import Any
try:
from scripts.rectification_policy import (
MIN_CONFIRMATION_DOMAINS,
MIN_CONFIRMATION_EVENTS,
)
except ModuleNotFoundError: # pragma: no cover - direct script execution
from rectification_policy import MIN_CONFIRMATION_DOMAINS, MIN_CONFIRMATION_EVENTS
def _gate(status: str, reason: str) -> dict[str, str]:
return {"status": status, "reason": reason}
@@ -21,9 +29,9 @@ def build_rectification_technique_contract(
external_validation: dict[str, Any] | None = None,
) -> dict[str, Any]:
blockers: list[str] = []
if event_count < 3:
if event_count < MIN_CONFIRMATION_EVENTS:
blockers.append("insufficient_events")
if domain_count < 2:
if domain_count < MIN_CONFIRMATION_DOMAINS:
blockers.append("insufficient_domains")
neighbor = (stability_diagnostics or {}).get("neighbor_stability") or {}
leave_one_out = (stability_diagnostics or {}).get("leave_one_event_out") or {}
@@ -38,17 +46,17 @@ def build_rectification_technique_contract(
elif external_status != "pass":
blockers.extend(external.get("blockers") or ["vedastro_validation_not_passed"])
confirmation_allowed = (
event_count >= 3
and domain_count >= 2
event_count >= MIN_CONFIRMATION_EVENTS
and domain_count >= MIN_CONFIRMATION_DOMAINS
and local_candidate_ready
and required_layers_complete
and high_rigor
and external_status == "pass"
)
gates = {
"event_quality": _gate("pass" if event_count >= 3 else "fail", "requires_at_least_three_dated_events"),
"cross_domain_coverage": _gate("pass" if domain_count >= 2 else "fail", "requires_at_least_two_event_domains"),
"local_candidate": _gate("pass" if local_candidate_ready else "fail", "requires_a_unique_leading_candidate_range_no_wider_than_fifteen_minutes"),
"event_quality": _gate("pass" if event_count >= MIN_CONFIRMATION_EVENTS else "fail", "requires_confirmation_event_count"),
"cross_domain_coverage": _gate("pass" if domain_count >= MIN_CONFIRMATION_DOMAINS else "fail", "requires_confirmation_domain_count"),
"local_candidate": _gate("pass" if local_candidate_ready else "fail", "requires_final_confirmation_width_and_margin_policy"),
"required_layers": _gate("pass" if required_layers_complete else "fail", "all_event_required_layers_must_compute"),
"neighbor_stability": _gate("pass" if neighbor.get("all_required_passed") else "diagnostic_fail", "diagnostic_only_unique_lead_at_plus_minus_1_2_5_minutes"),
"leave_one_event_out": _gate("pass" if leave_one_out.get("status") == "pass" else "diagnostic_fail", "diagnostic_only_leader_survival_after_removing_each_event"),