feat(rectification): 出卡加精度门槛,补经历改成系统点名
宽度超过 10 分钟或头名并列时不再出交付卡,改为按大运边界逐条问、 用类型芯片和年/月选择器录入。跳过的线换问法再问一次;答「这类事 都没有过」的不再问。用户说「没有了」仍立刻给目前范围。Skill 10.0.27。 BUG-740~743
This commit is contained in:
@@ -1779,6 +1779,203 @@ def discriminating_event_probes(
|
||||
return probes
|
||||
|
||||
|
||||
GUIDED_COLLECT_LIMIT = 6
|
||||
GUIDED_DOMAIN_ORDER = (
|
||||
"education",
|
||||
"career",
|
||||
"relocation",
|
||||
"relationship",
|
||||
"family",
|
||||
"finance",
|
||||
"health_pressure",
|
||||
)
|
||||
_GUIDED_TRACK_DOMAIN = {
|
||||
"nara:d9": "relationship",
|
||||
"nara:d10": "career",
|
||||
}
|
||||
|
||||
|
||||
def _guided_track_starts(
|
||||
context: dict[str, Any],
|
||||
*,
|
||||
birth_date: str,
|
||||
lo: int,
|
||||
hi: int,
|
||||
include_pratyantar: bool,
|
||||
varga_narayana: bool,
|
||||
vim_cache: dict[tuple[Any, ...], list[date]],
|
||||
narayana_cache: dict[tuple[Any, ...], list[date] | None],
|
||||
) -> dict[str, list[date]]:
|
||||
tracks: dict[str, list[date]] = {}
|
||||
moon = float(context["planet_longitudes"]["Moon"])
|
||||
vim_key = (*_vim_cache_key(birth_date, moon, lo, hi), include_pratyantar)
|
||||
if vim_key not in vim_cache:
|
||||
vim_cache[vim_key] = _vim_start_dates(
|
||||
birth_date, moon, lo, hi, include_pratyantar=include_pratyantar,
|
||||
)
|
||||
tracks["vim"] = vim_cache[vim_key]
|
||||
natal_key = (*_narayana_cache_key(
|
||||
int(context["ascendant_index"]), context["planet_longitudes"], birth_date, lo, hi,
|
||||
), None)
|
||||
if natal_key not in narayana_cache:
|
||||
narayana_cache[natal_key] = _narayana_start_dates(
|
||||
int(context["ascendant_index"]), context["planet_longitudes"], birth_date, lo, hi,
|
||||
)
|
||||
natal = narayana_cache[natal_key]
|
||||
if natal is not None:
|
||||
tracks["nara"] = natal
|
||||
if not varga_narayana:
|
||||
return tracks
|
||||
for layer in ("d9", "d10"):
|
||||
raw = _layer_value(context, layer)
|
||||
if not isinstance(raw, int):
|
||||
continue
|
||||
layer_key = (*_narayana_cache_key(
|
||||
raw, context["planet_longitudes"], birth_date, lo, hi,
|
||||
), layer)
|
||||
if layer_key not in narayana_cache:
|
||||
narayana_cache[layer_key] = _narayana_start_dates(
|
||||
raw, context["planet_longitudes"], birth_date, lo, hi,
|
||||
)
|
||||
dates = narayana_cache[layer_key]
|
||||
if dates is not None:
|
||||
tracks[f"nara:{layer}"] = dates
|
||||
return tracks
|
||||
|
||||
|
||||
def _guided_year_windows(dates: Sequence[date]) -> list[tuple[int, int, int]]:
|
||||
by_year: dict[int, list[date]] = {}
|
||||
for item in dates:
|
||||
by_year.setdefault(item.year, []).append(item)
|
||||
windows: list[tuple[int, int, int]] = []
|
||||
for year in sorted(by_year):
|
||||
months = [item.month for item in by_year[year]]
|
||||
windows.append((year, min(months), max(months)))
|
||||
return windows
|
||||
|
||||
|
||||
def guided_collect_windows(
|
||||
request: dict[str, Any],
|
||||
built: dict[str, Any],
|
||||
*,
|
||||
candidate_times: Sequence[str],
|
||||
today: date | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Boundary windows for guided collect. Does not change auto probe generation."""
|
||||
birth_date = str(request.get("birth_date") or "").strip()
|
||||
birth_year = _birth_year(birth_date)
|
||||
if birth_year is None:
|
||||
return []
|
||||
try:
|
||||
datetime.strptime(birth_date, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
return []
|
||||
remaining = _remaining_contexts(built, candidate_times)
|
||||
if len(remaining) < 2:
|
||||
remaining = _static_contexts(built)
|
||||
work = [item for item in remaining if _scoreable(item)]
|
||||
if len(work) < 2:
|
||||
return []
|
||||
declined = {
|
||||
str(item).strip()
|
||||
for item in (request.get("declined_domains") or [])
|
||||
if str(item).strip() in GUIDED_DOMAIN_ORDER
|
||||
}
|
||||
now = today or date.today()
|
||||
lo, hi = birth_year + 5, min(now.year, birth_year + 80)
|
||||
remaining_layers = _differing_layers(work)
|
||||
fallback = [
|
||||
domain
|
||||
for domain in _probe_domains(
|
||||
remaining_layers,
|
||||
[item for item in (request.get("events") or []) if isinstance(item, dict)],
|
||||
d1_differs="d1" in remaining_layers,
|
||||
)
|
||||
if domain not in declined
|
||||
]
|
||||
if not fallback:
|
||||
fallback = [domain for domain in GUIDED_DOMAIN_ORDER if domain not in declined]
|
||||
if not fallback:
|
||||
return []
|
||||
vim_cache: dict[tuple[Any, ...], list[date]] = {}
|
||||
narayana_cache: dict[tuple[Any, ...], list[date] | None] = {}
|
||||
starts_by_time: dict[str, dict[str, list[date]]] = {}
|
||||
for context in work:
|
||||
time = _context_time(context)
|
||||
if not time:
|
||||
continue
|
||||
starts_by_time[time] = _guided_track_starts(
|
||||
context,
|
||||
birth_date=birth_date,
|
||||
lo=lo,
|
||||
hi=hi,
|
||||
include_pratyantar=True,
|
||||
varga_narayana=True,
|
||||
vim_cache=vim_cache,
|
||||
narayana_cache=narayana_cache,
|
||||
)
|
||||
rows: list[dict[str, Any]] = []
|
||||
seen: set[tuple[int, int, int, str]] = set()
|
||||
unlayered_index = 0
|
||||
for left, right in _representative_pairs(work):
|
||||
left_time = str(_context_time(left) or "")
|
||||
right_time = str(_context_time(right) or "")
|
||||
left_tracks = starts_by_time.get(left_time) or {}
|
||||
right_tracks = starts_by_time.get(right_time) or {}
|
||||
for track in ("vim", "nara", "nara:d9", "nara:d10"):
|
||||
left_dates = left_tracks.get(track) or []
|
||||
right_dates = right_tracks.get(track) or []
|
||||
if not left_dates or not right_dates:
|
||||
continue
|
||||
for index, (one, two) in enumerate(zip(left_dates, right_dates)):
|
||||
if one == two:
|
||||
continue
|
||||
gathered: list[date] = []
|
||||
for context in work:
|
||||
time = _context_time(context)
|
||||
dates = (starts_by_time.get(time or "") or {}).get(track) or []
|
||||
if index < len(dates):
|
||||
gathered.append(dates[index])
|
||||
if len(gathered) < 2 or min(gathered) == max(gathered):
|
||||
continue
|
||||
ordered = sorted(gathered)
|
||||
cut = ordered[0] + (ordered[-1] - ordered[0]) / 2
|
||||
left_n = sum(1 for item in gathered if item <= cut)
|
||||
right_n = len(gathered) - left_n
|
||||
if left_n < 1 or right_n < 1:
|
||||
continue
|
||||
track_domain = _GUIDED_TRACK_DOMAIN.get(track)
|
||||
if track_domain and track_domain not in declined:
|
||||
domain = track_domain
|
||||
else:
|
||||
domain = fallback[unlayered_index % len(fallback)]
|
||||
unlayered_index += 1
|
||||
if domain in declined:
|
||||
continue
|
||||
for year, month_lo, month_hi in _guided_year_windows(gathered):
|
||||
if year < lo or year > hi:
|
||||
continue
|
||||
key = (year, month_lo, month_hi, domain)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
rows.append({
|
||||
"year": year,
|
||||
"month_lo": month_lo,
|
||||
"month_hi": month_hi,
|
||||
"domain": domain,
|
||||
"split": {"left": left_n, "right": right_n},
|
||||
})
|
||||
rows.sort(key=lambda row: (
|
||||
abs(row["split"]["left"] - row["split"]["right"]),
|
||||
abs(now.year - int(row["year"])),
|
||||
-int(row["year"]),
|
||||
int(row["month_lo"]),
|
||||
str(row["domain"]),
|
||||
))
|
||||
return rows[:GUIDED_COLLECT_LIMIT]
|
||||
|
||||
|
||||
def prospective_windows_for_time(
|
||||
request: dict[str, Any],
|
||||
built: dict[str, Any],
|
||||
|
||||
Reference in New Issue
Block a user