- affliction_evidence / career_semantic_profile / career_analysis (functional roles, affliction signals, user-industry gate) - sade_sati.build_saturn_cycle_timeline with bisected ingress moments (FLG_SIDEREAL frame, BUG-1275) - Chesta profile candidates and PL9 AV cell profile as sidecars (shadbala.py / ashtakavarga.py stay frozen, ERR-110) - Raman Sahams in the annual pack; book rule packets + admission gate - consultation_native_layers: affliction_evidence, career_analysis, sade_sati_timeline, shadbala_chesta_profiles, book_rules, annual_tajika.raman_sahams Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbBXoyn7Hj3LCW3BUs5bZ8
136 lines
5.9 KiB
Python
136 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Book / web rule layer for the consultation output (BUG-1699).
|
|
|
|
Upstream yinduzhanxing keeps copyright-safe rule summaries in
|
|
``references/book_rule_packets.json`` (plus the web intakes
|
|
``web_rule_intake_2026_10_06.json`` and ``..._romance.json``, merged into the
|
|
packets) and gates them with ``scripts/book_admission_gate.build_report``:
|
|
only a ``validated_reference`` packet with a formula-grade receipt becomes a
|
|
runtime packet, and admission never implies Technique Audit execution.
|
|
|
|
This module reads the same files with the same gate and hands the
|
|
consultation one object: the runtime packets (rules a reading may cite as a
|
|
validated method), and the route's ``reference_only`` / ``candidate_mapped``
|
|
rules, each with its summary, boundary and whether this repository carries
|
|
an implementation. It computes nothing about the chart.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
PACKETS_PATH = ROOT / "references" / "book_rule_packets.json"
|
|
INTAKE_PATHS = (
|
|
ROOT / "references" / "web_rule_intake_2026_10_06.json",
|
|
ROOT / "references" / "web_rule_intake_2026_10_06_romance.json",
|
|
)
|
|
SCHEMA = "jyotish.book_rule_layer.v1"
|
|
# Rule ids are matched by these words to a consultation route. A rule with no
|
|
# match is listed under no route (it still counts in admission_counts).
|
|
ROUTE_KEYWORDS = {
|
|
"marriage": ("marriage", "spouse", "upapada", "meeting", "seventh", "darakaraka"),
|
|
"timing": ("dasha", "transit", "timing"),
|
|
"annual": ("annual", "pravesha", "varsha", "karana", "tithi", "sudarsana", "saptarishi"),
|
|
"career": ("career", "karma", "tenth"),
|
|
"wealth": ("wealth", "hora_lagna", "dhana"),
|
|
"general": ("interpretation", "strength", "arudha", "argala", "varga"),
|
|
}
|
|
CLAIM_BOUNDARY = (
|
|
"Admission state never implies Technique Audit execution. Only runtime_packets may be cited as a "
|
|
"validated method; reference_only and candidate_mapped rules may frame a question or a check, never "
|
|
"ground a conclusion or a date. Romance intake rules describe where/how a meeting may happen and must "
|
|
"not be used to date it."
|
|
)
|
|
|
|
|
|
def _load(path: Path) -> dict[str, Any]:
|
|
try:
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
except (OSError, ValueError):
|
|
return {}
|
|
return data if isinstance(data, dict) else {}
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def _registry() -> dict[str, Any]:
|
|
try:
|
|
from book_admission_gate import build_report
|
|
except ImportError: # pragma: no cover - package import path
|
|
from scripts.book_admission_gate import build_report
|
|
packets_doc = _load(PACKETS_PATH)
|
|
packets = [row for row in packets_doc.get("packets", []) if isinstance(row, dict) and row.get("rule_id")]
|
|
gate = build_report({"cards": []}, {"packets": packets})
|
|
intake_ids: dict[str, str] = {}
|
|
for path in INTAKE_PATHS:
|
|
for row in _load(path).get("packets", []):
|
|
if isinstance(row, dict) and row.get("rule_id"):
|
|
intake_ids[str(row["rule_id"])] = path.name
|
|
return {"packets": packets, "gate": gate, "intake_ids": intake_ids,
|
|
"copyright_boundary": packets_doc.get("copyright_boundary")}
|
|
|
|
|
|
def _implemented_here(packet: dict[str, Any]) -> bool | None:
|
|
targets = [t for t in packet.get("implementation_targets") or [] if isinstance(t, str) and t.startswith("scripts/")]
|
|
if not targets:
|
|
return None
|
|
return all((ROOT / target).is_file() for target in targets)
|
|
|
|
|
|
def _row(packet: dict[str, Any], runtime_ids: set[str], intake_ids: dict[str, str]) -> dict[str, Any]:
|
|
validation = packet.get("validation") if isinstance(packet.get("validation"), dict) else {}
|
|
return {
|
|
"rule_id": packet["rule_id"],
|
|
"source_id": packet.get("source_id"),
|
|
"admission_state": packet.get("admission_state"),
|
|
"runtime_usable": packet["rule_id"] in runtime_ids,
|
|
"summary": packet.get("non_verbatim_rule_summary"),
|
|
"assertion_boundary": packet.get("assertion_boundary"),
|
|
"page_locator": packet.get("page_locator"),
|
|
"validation_status": validation.get("status"),
|
|
"implemented_here": _implemented_here(packet),
|
|
"intake_file": intake_ids.get(packet["rule_id"]),
|
|
}
|
|
|
|
|
|
def route_rule_ids(rule_id: str) -> list[str]:
|
|
lowered = rule_id.lower()
|
|
return [route for route, words in ROUTE_KEYWORDS.items() if any(word in lowered for word in words)]
|
|
|
|
|
|
def build_book_rule_layer(routes: set[str] | list[str] | None = None) -> dict[str, Any]:
|
|
registry = _registry()
|
|
packets = registry["packets"]
|
|
if not packets:
|
|
return {"schema": SCHEMA, "status": "blocked", "reason": "book_rule_packets_missing"}
|
|
gate = registry["gate"]
|
|
runtime_ids = set(gate.get("runtime_packet_ids") or [])
|
|
intake_ids = registry["intake_ids"]
|
|
wanted = {str(route).lower() for route in (routes or [])}
|
|
counts: dict[str, int] = {}
|
|
for packet in packets:
|
|
state = str(packet.get("admission_state"))
|
|
counts[state] = counts.get(state, 0) + 1
|
|
runtime = [_row(p, runtime_ids, intake_ids) for p in packets if p["rule_id"] in runtime_ids]
|
|
route_rules = [
|
|
_row(p, runtime_ids, intake_ids)
|
|
for p in packets
|
|
if p["rule_id"] not in runtime_ids and wanted.intersection(route_rule_ids(p["rule_id"]))
|
|
]
|
|
return {
|
|
"schema": SCHEMA,
|
|
"status": "executed",
|
|
"source": "references/book_rule_packets.json + scripts/book_admission_gate.build_report",
|
|
"routes": sorted(wanted),
|
|
"admission_counts": dict(sorted(counts.items())),
|
|
"runtime_packets": runtime,
|
|
"route_reference_rules": route_rules,
|
|
"gate_failures": gate.get("failures") or [],
|
|
"intake_files": [path.name for path in INTAKE_PATHS if path.is_file()],
|
|
"copyright_boundary": registry.get("copyright_boundary"),
|
|
"claim_boundary": CLAIM_BOUNDARY,
|
|
}
|