fix(rectification): exhaustion exit, explain layer, range reading, unknown-time scan (BUG-565–568)
Keep askable cards after exhaustion, explain each probe, read the adopted credible range in reports and chat, and compare declared periods before the minute grid when the clock is unknown. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -96,7 +96,11 @@ def _candidate_datetimes(request: RectificationEventRequest) -> list[datetime]:
|
||||
minute_count = int((end - start).total_seconds() // 60) + 1
|
||||
if minute_count < 1 or minute_count > 1_440:
|
||||
raise RectificationEventCalculationError("candidate_range_out_of_bounds")
|
||||
return [start + timedelta(minutes=offset) for offset in range(minute_count)]
|
||||
raw_step = request.get("minute_step") if isinstance(request, dict) else None
|
||||
step = int(raw_step or 1)
|
||||
if step < 1 or step > 15:
|
||||
raise RectificationEventCalculationError("minute_step_out_of_bounds")
|
||||
return [start + timedelta(minutes=offset) for offset in range(0, minute_count, step)]
|
||||
|
||||
|
||||
def _active_vimshottari(
|
||||
@@ -627,7 +631,7 @@ def _canonical_input_contract(request: RectificationEventRequest) -> tuple[dict,
|
||||
"candidate_range": {
|
||||
"start_time": request["start_time"],
|
||||
"end_time": request["end_time"],
|
||||
"step_minutes": 1,
|
||||
"step_minutes": int(request.get("minute_step") or 1),
|
||||
},
|
||||
"location": {
|
||||
"latitude": float(request["lat"]),
|
||||
|
||||
@@ -75,6 +75,7 @@ class RectificationEventRequest(TypedDict):
|
||||
tz: float
|
||||
ayanamsa: NotRequired[str]
|
||||
node_mode: NotRequired[str]
|
||||
minute_step: NotRequired[int]
|
||||
events: list[LifeEvent]
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ HEAVY_COMPUTE_PATHS = frozenset(
|
||||
"/api/rectification/v5/score",
|
||||
"/api/rectification/v5/diagnostics",
|
||||
"/api/rectification/v5/vedastro-validate",
|
||||
"/api/rectification/v5/range_reading",
|
||||
"/api/rectification/v5/block_scan",
|
||||
"/api/dynamic_rectification_opportunities",
|
||||
"/api/dynamic_rectification_score",
|
||||
"/api/high_rigor_workflow",
|
||||
|
||||
@@ -10,7 +10,7 @@ import re
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
SCHEMA_VERSION = "jyotish.flexible_birth_time_profile.v1"
|
||||
MAX_CANDIDATE_MINUTES = 15
|
||||
MAX_CANDIDATE_MINUTES = 31
|
||||
_CLOCK_RE = re.compile(r"(?:[01]\d|2[0-3]):[0-5]\d")
|
||||
_PROHIBITED_AUTHORITY_FIELDS = frozenset({
|
||||
"approved_birth_time",
|
||||
@@ -223,7 +223,7 @@ def _normalize_candidate_times(
|
||||
if isinstance(candidate_times, (str, bytes)) or not isinstance(candidate_times, Sequence):
|
||||
raise FlexibleBirthTimeProfileError("candidate_times_required")
|
||||
if len(candidate_times) < 2 or len(candidate_times) > MAX_CANDIDATE_MINUTES:
|
||||
raise FlexibleBirthTimeProfileError("candidate_count_must_be_two_to_fifteen")
|
||||
raise FlexibleBirthTimeProfileError("candidate_count_must_be_two_to_thirty_one")
|
||||
values: list[datetime] = []
|
||||
for raw in candidate_times:
|
||||
if not _is_hh_mm(raw):
|
||||
@@ -244,7 +244,7 @@ def _normalize_candidates(candidates: Sequence[Mapping[str, Any]]) -> list[dict[
|
||||
if isinstance(candidates, (str, bytes)) or not isinstance(candidates, Sequence):
|
||||
raise FlexibleBirthTimeProfileError("candidates_required")
|
||||
if len(candidates) < 2 or len(candidates) > MAX_CANDIDATE_MINUTES:
|
||||
raise FlexibleBirthTimeProfileError("candidate_count_must_be_two_to_fifteen")
|
||||
raise FlexibleBirthTimeProfileError("candidate_count_must_be_two_to_thirty_one")
|
||||
rows: list[dict[str, Any]] = []
|
||||
for candidate in candidates:
|
||||
if not isinstance(candidate, Mapping):
|
||||
|
||||
@@ -3167,6 +3167,8 @@ TECHNIQUE_EXAMPLE_ENDPOINTS = {
|
||||
'/api/rectification/v5/score',
|
||||
'/api/rectification/v5/diagnostics',
|
||||
'/api/rectification/v5/vedastro-validate',
|
||||
'/api/rectification/v5/range_reading',
|
||||
'/api/rectification/v5/block_scan',
|
||||
'/api/rectification/v5/versions',
|
||||
'/api/relationship',
|
||||
'/api/remedies',
|
||||
@@ -3641,6 +3643,10 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
self._json(self._compute_rectification_v5_diagnostics(body))
|
||||
elif path == '/api/rectification/v5/vedastro-validate':
|
||||
self._json(self._compute_rectification_v5_vedastro_validate(body))
|
||||
elif path == '/api/rectification/v5/range_reading':
|
||||
self._json(self._compute_rectification_v5_range_reading(body))
|
||||
elif path == '/api/rectification/v5/block_scan':
|
||||
self._json(self._compute_rectification_v5_block_scan(body))
|
||||
elif path == '/api/rectification/v5/versions':
|
||||
self._json(self._compute_rectification_v5_versions())
|
||||
elif path == '/api/dynamic_rectification_opportunities':
|
||||
@@ -9448,6 +9454,28 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
},
|
||||
}
|
||||
|
||||
def _compute_rectification_v5_range_reading(self, body):
|
||||
from scripts.rectification.api_service import range_reading
|
||||
if not isinstance(body, dict):
|
||||
raise BadRequest('request body must be an object')
|
||||
try:
|
||||
payload = range_reading(body)
|
||||
except (KeyError, TypeError, ValueError) as exc:
|
||||
raise BadRequest(str(exc) or 'range_reading_invalid') from exc
|
||||
return {
|
||||
'success': True,
|
||||
'endpoint': 'rectification_v5_range_reading',
|
||||
**payload,
|
||||
}
|
||||
|
||||
def _compute_rectification_v5_block_scan(self, body):
|
||||
from scripts.rectification.api_service import block_scan
|
||||
return {
|
||||
'success': True,
|
||||
'endpoint': 'rectification_v5_block_scan',
|
||||
**block_scan(self._rectification_v5_request(body)),
|
||||
}
|
||||
|
||||
def _compute_active_rectification_events_v4(self, body):
|
||||
"""Compatibility projection; validation and calculations are owned by V5 services."""
|
||||
from scripts.rectification.api_service import score_candidates
|
||||
@@ -10324,6 +10352,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
'/api/rectification/v5/score': self._compute_rectification_v5_score,
|
||||
'/api/rectification/v5/diagnostics': self._compute_rectification_v5_diagnostics,
|
||||
'/api/rectification/v5/vedastro-validate': self._compute_rectification_v5_vedastro_validate,
|
||||
'/api/rectification/v5/range_reading': self._compute_rectification_v5_range_reading,
|
||||
'/api/rectification/v5/block_scan': self._compute_rectification_v5_block_scan,
|
||||
'/api/rectification/v5/versions': self._compute_rectification_v5_versions,
|
||||
'/api/relationship': self._compute_relationship,
|
||||
'/api/remedies': self._compute_remedies,
|
||||
@@ -10455,6 +10485,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
'/api/rectification/v5/score': 'Build the V5 event-by-candidate contribution matrix and score candidate ranges',
|
||||
'/api/rectification/v5/diagnostics': 'Run V5 stability diagnostics over the server-owned contribution matrix',
|
||||
'/api/rectification/v5/vedastro-validate': 'Validate one V5 primary/runner-up pair with safe official VedAstro summaries',
|
||||
'/api/rectification/v5/range_reading': 'Compare stable versus minute-sensitive themes across a trusted unresolved window',
|
||||
'/api/rectification/v5/block_scan': 'Score a 24-hour unknown-time case in declared period blocks',
|
||||
'/api/rectification/v5/versions': 'Return live V5 algorithm and decision-policy identity without scoring',
|
||||
'/api/relationship': 'Compute relationship and spouse-status evidence',
|
||||
'/api/remedies': 'Generate low-risk remedies from doshas/strength/dasha',
|
||||
@@ -10539,6 +10571,18 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
'candidate_times': ['05:01', '05:02'],
|
||||
'events': [{'id': '00000000-0000-4000-8000-000000000001', 'domain': 'education', 'event_kind': 'education_milestone', 'date_start': '2016-09-01', 'date_end': '2016-09-30', 'precision': 'month', 'summary': '大学入学'}],
|
||||
},
|
||||
'/api/rectification/v5/range_reading': {
|
||||
'birth_date': '1997-08-08',
|
||||
'lat': 36.419, 'lon': 114.213, 'tz': 8,
|
||||
'birth_time_accuracy': 'provisional',
|
||||
'representative_time': '05:01',
|
||||
'candidate_range': {'start_time': '05:00', 'end_time': '05:26', 'representative_time': '05:01'},
|
||||
},
|
||||
'/api/rectification/v5/block_scan': {
|
||||
'birth_date': '1997-08-08', 'start_time': '00:00', 'end_time': '23:59',
|
||||
'lat': 36.419, 'lon': 114.213, 'tz': 8, 'minute_step': 10,
|
||||
'events': [{'id': '00000000-0000-4000-8000-000000000001', 'domain': 'education', 'event_kind': 'education_milestone', 'date_start': '2016-09-01', 'date_end': '2016-09-30', 'precision': 'month', 'summary': '大学入学'}],
|
||||
},
|
||||
'/api/rectification/v5/versions': {},
|
||||
'/api/relationship': {'planets': SAMPLE_PLANETS, 'asc_sign': 'Aries', 'dasha_info': {'maha_dasha': 'Venus', 'antar_dasha': 'Jupiter'}},
|
||||
'/api/remedies': {'shadbala': {'Sun': {'rupas': 4.1}, 'Moon': {'rupas': 3.8}}, 'doshas': ['manglik'], 'dasha_lord': 'Venus'},
|
||||
|
||||
@@ -2188,11 +2188,40 @@ def _birth_time_candidate_window(args, center: datetime, accuracy: str) -> tuple
|
||||
return start, representative, end
|
||||
|
||||
|
||||
def _candidate_minutes(start: datetime, representative: datetime, end: datetime) -> list[str]:
|
||||
def _candidate_minutes(
|
||||
start: datetime,
|
||||
representative: datetime,
|
||||
end: datetime,
|
||||
*,
|
||||
coarse: bool = False,
|
||||
) -> list[str]:
|
||||
minute_count = int((end - start).total_seconds() // 60) + 1
|
||||
if minute_count <= 15:
|
||||
if minute_count <= 0:
|
||||
return [start.strftime("%H:%M")]
|
||||
if minute_count <= 31:
|
||||
return [(start + timedelta(minutes=index)).strftime("%H:%M") for index in range(minute_count)]
|
||||
return list(dict.fromkeys(value.strftime("%H:%M") for value in (start, representative, end)))
|
||||
if coarse:
|
||||
return list(dict.fromkeys(value.strftime("%H:%M") for value in (start, representative, end)))
|
||||
span = minute_count - 1
|
||||
representative = min(max(representative, start), end)
|
||||
rep_offset = int((representative - start).total_seconds() // 60)
|
||||
offsets = {0, span, rep_offset}
|
||||
for index in range(31):
|
||||
offsets.add(round(index * span / 30))
|
||||
ordered = sorted(offsets)
|
||||
while len(ordered) > 31:
|
||||
droppable = [offset for offset in ordered if offset not in (0, span, rep_offset)]
|
||||
if not droppable:
|
||||
break
|
||||
def crowding(offset: int) -> tuple[int, int]:
|
||||
nearest = min(abs(other - offset) for other in ordered if other != offset)
|
||||
return (nearest, offset)
|
||||
ordered.remove(min(droppable, key=crowding))
|
||||
missing = (index for index in range(minute_count) if index not in ordered)
|
||||
while len(ordered) < 31:
|
||||
ordered.append(next(missing))
|
||||
ordered.sort()
|
||||
return [(start + timedelta(minutes=offset)).strftime("%H:%M") for offset in ordered]
|
||||
|
||||
|
||||
def _build_birth_time_sensitivity(args) -> dict:
|
||||
@@ -2213,7 +2242,13 @@ def _build_birth_time_sensitivity(args) -> dict:
|
||||
"status": "not_applicable",
|
||||
"accuracy": "confirmed",
|
||||
}
|
||||
candidate_times = _candidate_minutes(start, representative, end)
|
||||
minute_count = int((end - start).total_seconds() // 60) + 1
|
||||
candidate_times = _candidate_minutes(
|
||||
start,
|
||||
representative,
|
||||
end,
|
||||
coarse=accuracy == "approximate" and minute_count > 31,
|
||||
)
|
||||
try:
|
||||
from flexible_birth_time_profile import build_flexible_birth_time_profile_from_window
|
||||
from flexible_birth_time_report_support import build_flexible_birth_time_report_support
|
||||
@@ -2240,11 +2275,13 @@ def _build_birth_time_sensitivity(args) -> dict:
|
||||
profile["birth_time_window"]["representative_time"] = representative.strftime("%H:%M")
|
||||
support = build_flexible_birth_time_report_support(profile)
|
||||
projection = build_flexible_birth_time_full_report_projection(support)
|
||||
window = dict(projection["window"] or {})
|
||||
window["sampled"] = minute_count > 31
|
||||
return {
|
||||
"schema": "jyotish.report_birth_time_sensitivity.v1",
|
||||
"status": "candidate_window_only",
|
||||
"accuracy": accuracy,
|
||||
"window": projection["window"],
|
||||
"window": window,
|
||||
"theme_sensitivity": projection["theme_sensitivity"],
|
||||
"stable_evidence": projection["stable_structure_section"],
|
||||
"sensitive_evidence": projection["minute_sensitive_section"],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Sequence
|
||||
from typing import Any, Mapping, Sequence
|
||||
from uuid import NAMESPACE_URL, uuid5
|
||||
|
||||
from scripts.active_rectification_events import build_candidate_result_summary
|
||||
@@ -303,3 +303,182 @@ def diagnostics(request: RectificationRequest) -> dict[str, Any]:
|
||||
"margin_percent": scored["margin_percent"],
|
||||
"can_confirm_exact_minute": scored["confirmation_allowed"],
|
||||
}
|
||||
|
||||
|
||||
def range_reading(request: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"""Stable vs minute-sensitive themes for one unresolved clock window."""
|
||||
from types import SimpleNamespace
|
||||
|
||||
from scripts.jyotish_engine import _build_birth_time_sensitivity
|
||||
|
||||
body = dict(request or {})
|
||||
birth_date = str(body.get("birth_date") or "").strip()
|
||||
if birth_date:
|
||||
year_text, month_text, day_text = birth_date.split("-", 2)
|
||||
year, month, day = int(year_text), int(month_text), int(day_text)
|
||||
else:
|
||||
year, month, day = int(body["year"]), int(body["month"]), int(body["day"])
|
||||
representative = str(body.get("representative_time") or "")[:5]
|
||||
if len(representative) == 5 and representative[2] == ":":
|
||||
hour, minute = int(representative[:2]), int(representative[3:])
|
||||
else:
|
||||
hour = int(body.get("hour") or 12)
|
||||
minute = int(body.get("minute") or 0)
|
||||
representative = f"{hour:02d}:{minute:02d}"
|
||||
raw_range = body.get("candidate_range")
|
||||
if isinstance(raw_range, Mapping):
|
||||
start_time = str(raw_range.get("start_time") or "")[:5]
|
||||
end_time = str(raw_range.get("end_time") or "")[:5]
|
||||
representative = str(raw_range.get("representative_time") or representative)[:5]
|
||||
else:
|
||||
start_time = str(body.get("start_time") or "")[:5]
|
||||
end_time = str(body.get("end_time") or "")[:5]
|
||||
hour, minute = int(representative[:2]), int(representative[3:])
|
||||
accuracy = str(body.get("birth_time_accuracy") or "provisional")
|
||||
args = SimpleNamespace(
|
||||
year=year,
|
||||
month=month,
|
||||
day=day,
|
||||
hour=hour,
|
||||
minute=minute,
|
||||
second=0,
|
||||
lat=float(body["lat"]),
|
||||
lon=float(body["lon"]),
|
||||
tz=float(body["tz"]),
|
||||
ayanamsa=body.get("ayanamsa") or "raman",
|
||||
node_mode=body.get("node_mode") or "mean",
|
||||
birth_time_accuracy=accuracy,
|
||||
candidate_range={
|
||||
"start_time": start_time,
|
||||
"end_time": end_time,
|
||||
"representative_time": representative,
|
||||
},
|
||||
representative_time=representative,
|
||||
declared_window_start=None,
|
||||
declared_window_end=None,
|
||||
uncertainty_before_minutes=None,
|
||||
uncertainty_after_minutes=None,
|
||||
)
|
||||
sensitivity = _build_birth_time_sensitivity(args)
|
||||
themes = sensitivity.get("theme_sensitivity")
|
||||
themes = themes if isinstance(themes, dict) else {}
|
||||
stable = [
|
||||
key for key, row in themes.items()
|
||||
if isinstance(row, dict) and row.get("status") == "stable"
|
||||
]
|
||||
sensitive = [
|
||||
key for key, row in themes.items()
|
||||
if isinstance(row, dict) and row.get("status") == "sensitive"
|
||||
]
|
||||
return {
|
||||
"window": sensitivity.get("window"),
|
||||
"stable_themes": stable,
|
||||
"sensitive_themes": sensitive,
|
||||
"claim_boundary": sensitivity.get("claim_boundary"),
|
||||
"theme_sensitivity": themes,
|
||||
"accuracy": sensitivity.get("accuracy"),
|
||||
"status": sensitivity.get("status"),
|
||||
}
|
||||
|
||||
|
||||
BLOCK_SCAN_PERIODS: tuple[tuple[str, str, str], ...] = (
|
||||
("early_morning", "04:00", "07:59"),
|
||||
("morning", "08:00", "11:59"),
|
||||
("afternoon", "12:00", "17:59"),
|
||||
("evening", "18:00", "22:59"),
|
||||
("late_night", "23:00", "03:59"),
|
||||
)
|
||||
|
||||
|
||||
def _clock_in_declared_period(clock: str, start_time: str, end_time: str) -> bool:
|
||||
current = _clock_minutes(clock[:5])
|
||||
start = _clock_minutes(start_time)
|
||||
end = _clock_minutes(end_time)
|
||||
if start <= end:
|
||||
return start <= current <= end
|
||||
return current >= start or current <= end
|
||||
|
||||
|
||||
def _normalize_relative_support(raw: Sequence[float]) -> list[float]:
|
||||
floored = [max(0.0, float(value)) for value in raw]
|
||||
total = sum(floored)
|
||||
if total <= 0:
|
||||
return [20.0 for _ in floored]
|
||||
shares = [round(100.0 * value / total, 1) for value in floored]
|
||||
delta = round(100.0 - sum(shares), 1)
|
||||
if shares:
|
||||
shares[shares.index(max(shares))] = round(shares[shares.index(max(shares))] + delta, 1)
|
||||
return shares
|
||||
|
||||
|
||||
def block_scan(request: RectificationRequest) -> dict[str, Any]:
|
||||
"""Aggregate 24h event scores into the five declared birth-time periods."""
|
||||
step = int(request.get("minute_step") or 10)
|
||||
if step <= 1:
|
||||
step = 10
|
||||
scoring_request = {**request, "minute_step": step}
|
||||
scored = score_candidates(scoring_request)
|
||||
events_by_id = {
|
||||
str(event.get("id") or ""): event
|
||||
for event in request.get("events") or []
|
||||
if isinstance(event, dict)
|
||||
}
|
||||
rows = [
|
||||
row for row in scored.get("candidate_scores") or []
|
||||
if isinstance(row, dict) and str(row.get("time") or "")[:5]
|
||||
]
|
||||
raw_support: list[float] = []
|
||||
blocks: list[dict[str, Any]] = []
|
||||
for period, start_time, end_time in BLOCK_SCAN_PERIODS:
|
||||
members = [
|
||||
row for row in rows
|
||||
if _clock_in_declared_period(str(row.get("time") or "")[:5], start_time, end_time)
|
||||
]
|
||||
counts: dict[str, int] = {}
|
||||
for row in members:
|
||||
for event_id in row.get("supporting_event_ids") or []:
|
||||
key = str(event_id)
|
||||
if key:
|
||||
counts[key] = counts.get(key, 0) + 1
|
||||
top_events = []
|
||||
for event_id, _count in sorted(counts.items(), key=lambda item: (-item[1], item[0]))[:3]:
|
||||
event = events_by_id.get(event_id) or {}
|
||||
top_events.append({
|
||||
"event_id": event_id,
|
||||
"domain": event.get("domain"),
|
||||
"summary": event.get("summary"),
|
||||
})
|
||||
raw_support.append(sum(float(row.get("score") or 0) for row in members))
|
||||
blocks.append({
|
||||
"period": period,
|
||||
"start_time": start_time,
|
||||
"end_time": end_time,
|
||||
"relative_support": 0,
|
||||
"top_events": top_events,
|
||||
"candidate_count": len(members),
|
||||
})
|
||||
shares = _normalize_relative_support(raw_support)
|
||||
for block, share in zip(blocks, shares):
|
||||
block["relative_support"] = share
|
||||
receipt = scored.get("decision_receipt") if isinstance(scored.get("decision_receipt"), dict) else {}
|
||||
return {
|
||||
"result_id": scored.get("result_id"),
|
||||
"algorithm_version": scored.get("algorithm_version"),
|
||||
"calculation_spec": scored.get("calculation_spec"),
|
||||
"calculation_spec_hash": scored.get("calculation_spec_hash"),
|
||||
"minute_step": step,
|
||||
"candidate_count": len(rows),
|
||||
"precision_stage": {"current": "block_scan"},
|
||||
"blocks": blocks,
|
||||
"discriminating_event_probes": [],
|
||||
"acceptance_allowed": False,
|
||||
"selection_allowed": False,
|
||||
"display_allowed": False,
|
||||
"decision_receipt": {
|
||||
**receipt,
|
||||
"precision_stage": {"current": "block_scan"},
|
||||
"discriminating_event_probes": [],
|
||||
"acceptance_allowed": False,
|
||||
"selection_allowed": False,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ _EVENT_PROVENANCE_FIELDS = frozenset({
|
||||
})
|
||||
_REQUEST_FIELDS = frozenset({
|
||||
"birth_date", "start_time", "end_time", "lat", "lon", "tz", "events",
|
||||
"ayanamsa", "node_mode", "asked_probe_keys",
|
||||
"ayanamsa", "node_mode", "asked_probe_keys", "minute_step",
|
||||
}) | _REQUEST_PROVENANCE_FIELDS
|
||||
_EVENT_FIELDS = frozenset({"id", "domain", "event_kind", "date_start", "date_end", "precision", "summary"}) | _EVENT_PROVENANCE_FIELDS
|
||||
_CLOCK = re.compile(r"(?:[01]\d|2[0-3]):[0-5]\d\Z")
|
||||
@@ -89,6 +89,7 @@ class RectificationRequest(TypedDict):
|
||||
timezone_source: NotRequired[str | None]
|
||||
local_time_status: NotRequired[str | None]
|
||||
asked_probe_keys: NotRequired[list[str]]
|
||||
minute_step: NotRequired[int]
|
||||
|
||||
|
||||
JsonObject = dict[str, Any]
|
||||
@@ -266,4 +267,10 @@ def normalize_rectification_request(body: Any, *, today: date | None = None) ->
|
||||
seen.add(key)
|
||||
cleaned_keys.append(key)
|
||||
cleaned_request["asked_probe_keys"] = cleaned_keys
|
||||
if "minute_step" in body:
|
||||
minute_step = body.get("minute_step")
|
||||
if isinstance(minute_step, bool) or not isinstance(minute_step, int) or not 1 <= minute_step <= 15:
|
||||
raise ValueError("minute_step must be an integer from 1 to 15")
|
||||
if minute_step != 1:
|
||||
cleaned_request["minute_step"] = minute_step
|
||||
return cast(RectificationRequest, cleaned_request)
|
||||
|
||||
@@ -563,6 +563,7 @@ def build_decision_receipt(
|
||||
representative_time=representative["time"] if representative else None,
|
||||
candidate_times=grid_times,
|
||||
cluster_width_minutes=width,
|
||||
include_discriminators=int(request.get("minute_step") or 1) <= 1,
|
||||
)
|
||||
if packet["dasha_agreement"]["status"] == "conflict":
|
||||
if overall_confidence == "high":
|
||||
|
||||
@@ -548,12 +548,33 @@ def build_refinement_packet(
|
||||
representative_time: str | None,
|
||||
candidate_times: Sequence[str],
|
||||
cluster_width_minutes: int | None = None,
|
||||
include_discriminators: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
scan = window_scan(built)
|
||||
cluster = cluster_scan(built, candidate_times, representative_time, cluster_width_minutes)
|
||||
ledger = event_dasha_ledger(request, built, representative_time)
|
||||
agreement = dasha_agreement(built, candidate_times)
|
||||
stage = precision_stage(cluster, len(request.get("events") or []))
|
||||
if not include_discriminators:
|
||||
return {
|
||||
"window_scan": scan,
|
||||
"event_dasha_ledger": ledger,
|
||||
"event_fit_rate": event_fit_rate(ledger),
|
||||
"dasha_agreement": agreement,
|
||||
"lagna_contrast": lagna_contrast(built),
|
||||
"nakshatra_boundary": nakshatra_boundary(built, representative_time),
|
||||
"precision_stage": {"current": "block_scan"},
|
||||
"oos_blind_prompts": [],
|
||||
"discriminating_event_probes": [],
|
||||
"event_clarification_probes": [],
|
||||
"evidence_collection_probes": [],
|
||||
"candidate_contrast_opportunities": [],
|
||||
"holdout_validation_probes": [],
|
||||
"dropped_probes": [],
|
||||
"prospective_probes": [],
|
||||
"unique_minute_claim": False,
|
||||
"confirmation_allowed": False,
|
||||
}
|
||||
from scripts.rectification.candidate_contrast import PROBE_PHASE_HOLDOUT_VALIDATION, event_year
|
||||
from scripts.rectification.case_holdout import reserved_holdout_events
|
||||
from scripts.rectification.event_probes import (
|
||||
|
||||
@@ -116,7 +116,7 @@ def _legacy_request(request: RectificationRequest, event: LifeEvent, sampled_dat
|
||||
"date": sampled_date, "precision": "day", "summary": event.get("summary", ""),
|
||||
}],
|
||||
}
|
||||
for key in ("ayanamsa", "node_mode"):
|
||||
for key in ("ayanamsa", "node_mode", "minute_step"):
|
||||
if key in request:
|
||||
legacy_request[key] = request[key]
|
||||
return legacy_request
|
||||
@@ -355,7 +355,7 @@ def calculation_spec(request: RectificationRequest) -> dict[str, Any]:
|
||||
"timezoneOffsetHours": json_number(request["tz"]),
|
||||
"ayanamsa": request.get("ayanamsa", "raman"),
|
||||
"nodeMode": request.get("node_mode", "mean"),
|
||||
"minuteStep": 1,
|
||||
"minuteStep": int(request.get("minute_step") or 1),
|
||||
}
|
||||
for source, target in (
|
||||
("birth_time_source", "birthTimeSource"),
|
||||
|
||||
Reference in New Issue
Block a user