chore(release): harden premium skill packaging
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Cross-system Jyotish/Western evidence arbitration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _packet_status(packet: dict[str, Any] | None) -> str:
|
||||
if not isinstance(packet, dict) or not packet:
|
||||
return "blocked"
|
||||
return str(packet.get("status") or "partial")
|
||||
|
||||
|
||||
def _signals(packet: dict[str, Any] | None) -> list[dict[str, Any]]:
|
||||
if not isinstance(packet, dict):
|
||||
return []
|
||||
raw = packet.get("signals")
|
||||
if not isinstance(raw, list):
|
||||
raw = packet.get("cross_system_signals")
|
||||
if not isinstance(raw, list):
|
||||
return []
|
||||
signals: list[dict[str, Any]] = []
|
||||
for item in raw:
|
||||
if isinstance(item, dict) and item.get("theme") and item.get("claim"):
|
||||
signals.append(
|
||||
{
|
||||
"theme": str(item.get("theme")),
|
||||
"claim": str(item.get("claim")),
|
||||
"timing": str(item.get("timing") or "unspecified"),
|
||||
"source": str(item.get("source") or "unspecified"),
|
||||
}
|
||||
)
|
||||
return signals
|
||||
|
||||
|
||||
def _audit_row(name: str, status: str, used: bool, effect: str) -> dict[str, Any]:
|
||||
return {
|
||||
"technique": name,
|
||||
"status": status,
|
||||
"used": used,
|
||||
"effect_on_confidence": effect,
|
||||
}
|
||||
|
||||
|
||||
def build_cross_system_arbitration(
|
||||
*,
|
||||
route_packet: dict[str, Any],
|
||||
jyotish_evidence: dict[str, Any] | None,
|
||||
western_evidence: dict[str, Any] | None,
|
||||
) -> dict[str, Any]:
|
||||
"""Compare materialized Jyotish and Western evidence packets."""
|
||||
|
||||
primary_theme = str(route_packet.get("primary_theme") or route_packet.get("question_type") or "general")
|
||||
jyotish_status = _packet_status(jyotish_evidence)
|
||||
western_status = _packet_status(western_evidence)
|
||||
blocked_items: list[str] = []
|
||||
if jyotish_status == "blocked":
|
||||
blocked_items.append("jyotish_evidence_packet_missing")
|
||||
if western_status == "blocked":
|
||||
blocked_items.append("western_evidence_packet_missing")
|
||||
|
||||
jyotish_signals = _signals(jyotish_evidence)
|
||||
western_signals = _signals(western_evidence)
|
||||
shared: list[dict[str, Any]] = []
|
||||
conflicts: list[dict[str, Any]] = []
|
||||
|
||||
for jyotish_signal in jyotish_signals:
|
||||
for western_signal in western_signals:
|
||||
if jyotish_signal["theme"] != western_signal["theme"]:
|
||||
continue
|
||||
if jyotish_signal["claim"] == western_signal["claim"]:
|
||||
shared.append(
|
||||
{
|
||||
"theme": jyotish_signal["theme"],
|
||||
"claim": jyotish_signal["claim"],
|
||||
"timing": (
|
||||
jyotish_signal["timing"]
|
||||
if jyotish_signal["timing"] == western_signal["timing"]
|
||||
else f"{jyotish_signal['timing']} | western:{western_signal['timing']}"
|
||||
),
|
||||
"jyotish_source": jyotish_signal["source"],
|
||||
"western_source": western_signal["source"],
|
||||
"confidence_effect": "raises_confidence",
|
||||
}
|
||||
)
|
||||
else:
|
||||
conflicts.append(
|
||||
{
|
||||
"theme": jyotish_signal["theme"],
|
||||
"jyotish_claim": jyotish_signal["claim"],
|
||||
"western_claim": western_signal["claim"],
|
||||
"jyotish_timing": jyotish_signal["timing"],
|
||||
"western_timing": western_signal["timing"],
|
||||
"confidence_effect": "lowers_confidence",
|
||||
}
|
||||
)
|
||||
|
||||
if blocked_items:
|
||||
status = "blocked"
|
||||
elif conflicts and not shared:
|
||||
status = "conflict"
|
||||
elif shared:
|
||||
status = "used"
|
||||
else:
|
||||
status = "partial"
|
||||
|
||||
return {
|
||||
"status": status,
|
||||
"primary_theme": primary_theme,
|
||||
"jyotish_cross_validation": {"status": jyotish_status, "signal_count": len(jyotish_signals)},
|
||||
"western_cross_validation": {"status": western_status, "signal_count": len(western_signals)},
|
||||
"shared_signals": shared,
|
||||
"conflicts": conflicts,
|
||||
"blocked_items": blocked_items,
|
||||
"technique_audit_rows": [
|
||||
_audit_row(
|
||||
"Western Cross-Validation",
|
||||
western_status,
|
||||
western_status in {"complete", "used"} or bool(western_signals),
|
||||
"western_oracle_available_for_trigger_refinement"
|
||||
if western_status != "blocked"
|
||||
else "blocked_until_western_evidence_packet_is_materialized",
|
||||
),
|
||||
_audit_row(
|
||||
"Cross-System Arbitration",
|
||||
status,
|
||||
status == "used",
|
||||
"shared_signals_raise_confidence"
|
||||
if status == "used"
|
||||
else "confidence_capped_until_systems_share_a_theme_signal",
|
||||
),
|
||||
],
|
||||
"boundary": (
|
||||
"Arbitration compares materialized evidence only; it does not replace Jyotish adjudication or "
|
||||
"claim Western calculations were run when the western packet is missing."
|
||||
),
|
||||
}
|
||||
@@ -10,10 +10,12 @@ try:
|
||||
from diagnose_vedastro_mode import build_report as build_vedastro_report
|
||||
from diagnose_pyjhora_adapter import build_report as build_pyjhora_report
|
||||
from diagnose_jyotishganit_adapter import build_report as build_jyotishganit_report
|
||||
from three_engine_parity_replay_validator import validate_manifest as validate_parity_replay_manifest
|
||||
except Exception: # pragma: no cover - import path varies in tests/CLI
|
||||
from scripts.diagnose_vedastro_mode import build_report as build_vedastro_report
|
||||
from scripts.diagnose_pyjhora_adapter import build_report as build_pyjhora_report
|
||||
from scripts.diagnose_jyotishganit_adapter import build_report as build_jyotishganit_report
|
||||
from scripts.three_engine_parity_replay_validator import validate_manifest as validate_parity_replay_manifest
|
||||
|
||||
|
||||
REQUIRED_PARITY_OUTPUTS = ["D1", "D9", "D10", "D2", "D4", "Vimshottari", "Shadbala", "Ashtakavarga"]
|
||||
@@ -29,6 +31,7 @@ def _same_chart_parity_contract(engines: dict) -> dict:
|
||||
"blocked": not available,
|
||||
"blocking_reason": "" if available else engine["status"],
|
||||
}
|
||||
replay_manifest = validate_parity_replay_manifest("references/oracle/three_engine_parity_replay_manifest.json")
|
||||
return {
|
||||
"status": "ready" if all(state["available"] for state in engine_states.values()) else "blocked",
|
||||
"required_outputs": REQUIRED_PARITY_OUTPUTS,
|
||||
@@ -56,6 +59,7 @@ def _same_chart_parity_contract(engines: dict) -> dict:
|
||||
"jyotishganit": ["raw_output_path", "panchanga", "tithi", "nakshatra", "yoga", "karana"],
|
||||
},
|
||||
"engine_states": engine_states,
|
||||
"replay_manifest": replay_manifest,
|
||||
"boundary": "This is a parity contract, not proof that the same-chart comparison has run.",
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ try:
|
||||
from scripts.unified_consultation_orchestrator import UnifiedConsultationOrchestrator
|
||||
except ModuleNotFoundError: # pragma: no cover - script execution path
|
||||
from unified_consultation_orchestrator import UnifiedConsultationOrchestrator
|
||||
try:
|
||||
from scripts.western_oracle_adapter import build_packet_from_oracle_payload
|
||||
except ModuleNotFoundError: # pragma: no cover - script execution path
|
||||
from western_oracle_adapter import build_packet_from_oracle_payload
|
||||
|
||||
SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
REPO_ROOT = os.path.abspath(os.path.join(SCRIPTS_DIR, '..'))
|
||||
@@ -41,6 +45,27 @@ _HIGH_RIGOR_JOB_SCOPE = 'high_rigor_workflow'
|
||||
_UNIFIED_CONSULTATION_ORCHESTRATOR = UnifiedConsultationOrchestrator()
|
||||
|
||||
|
||||
def _western_evidence_packet_from_body(body: dict, route_packet: dict) -> dict | None:
|
||||
explicit_packet = body.get('western_evidence_packet')
|
||||
if isinstance(explicit_packet, dict):
|
||||
return explicit_packet
|
||||
oracle_payload = body.get('western_oracle_payload') or body.get('western_astrology_oracle')
|
||||
if not isinstance(oracle_payload, dict):
|
||||
return None
|
||||
try:
|
||||
return build_packet_from_oracle_payload(oracle_payload, route_packet=route_packet)
|
||||
except Exception as exc: # pragma: no cover - defensive contract boundary
|
||||
return {
|
||||
'system': 'western_astrology',
|
||||
'status': 'blocked',
|
||||
'route': dict(route_packet),
|
||||
'signals': [],
|
||||
'missing_sections': ['western_oracle_payload'],
|
||||
'adapter_error': exc.__class__.__name__,
|
||||
'boundary': 'Western oracle payload was supplied but could not be normalized.',
|
||||
}
|
||||
|
||||
|
||||
def execute_consultation_workflow(
|
||||
handler,
|
||||
*,
|
||||
@@ -55,6 +80,7 @@ def execute_consultation_workflow(
|
||||
entry_mode = body.get('entry_mode', 'direct_chart')
|
||||
high_rigor = bool(body.get('return_high_rigor_shape'))
|
||||
route_packet = _UNIFIED_CONSULTATION_ORCHESTRATOR.resolve_route(question, themes)
|
||||
western_evidence_packet = _western_evidence_packet_from_body(body, route_packet)
|
||||
unified_contract = _UNIFIED_CONSULTATION_ORCHESTRATOR.shared_contract(
|
||||
entry_mode=entry_mode,
|
||||
question=question,
|
||||
@@ -97,8 +123,11 @@ def execute_consultation_workflow(
|
||||
route_packet=route_packet,
|
||||
executed_steps=[],
|
||||
skipped_steps=known_steps,
|
||||
western_evidence_packet=western_evidence_packet,
|
||||
blind=bool(body.get('blind') or body.get('blind_technical_mode')),
|
||||
)
|
||||
if western_evidence_packet:
|
||||
result['western_evidence_packet'] = western_evidence_packet
|
||||
if body.get('return_high_rigor_shape'):
|
||||
result['endpoint'] = 'high_rigor_workflow'
|
||||
return result
|
||||
@@ -207,6 +236,7 @@ def execute_consultation_workflow(
|
||||
interpretation_source_runtime_coverage=interpretation_source_runtime_coverage,
|
||||
machine_evidence_packet=machine_evidence_packet,
|
||||
real_case_calibration=real_case_calibration,
|
||||
western_evidence_packet=western_evidence_packet,
|
||||
blind=bool(body.get('blind') or body.get('blind_technical_mode')),
|
||||
)
|
||||
|
||||
@@ -253,6 +283,7 @@ def execute_consultation_workflow(
|
||||
'runtime_truth': runtime_truth,
|
||||
'interpretation_source_runtime_coverage': interpretation_source_runtime_coverage,
|
||||
'machine_evidence_packet': machine_evidence_packet,
|
||||
'western_evidence_packet': western_evidence_packet or {},
|
||||
'real_case_calibration': real_case_calibration,
|
||||
'runtime_evidence_log': runtime_evidence_log,
|
||||
'next_questions': handler._high_rigor_next_questions(rectification, historical_backtest),
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
@@ -40,28 +41,38 @@ TEXT_SUFFIXES = {
|
||||
".yml",
|
||||
}
|
||||
|
||||
DENY_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
|
||||
("private_exact_iso_birth_date", re.compile(r"REDACTED_DATE")),
|
||||
("private_compact_birth_datetime", re.compile(r"REDACTED_DATE[_-]?REDACTED_TIME")),
|
||||
("private_slug_birth_date", re.compile(r"REDACTED_YEAR[_-]04[_-]17")),
|
||||
("private_birth_time_literal", re.compile(r"REDACTED_YEAR.{0,120}\bREDACTED_TIME\b|\bREDACTED_TIME\b.{0,120}REDACTED_YEAR|REDACTED_TIME")),
|
||||
("private_place_han", re.compile(r"REDACTED_PLACE|REDACTED_PLACE|REDACTED_HOSPITAL")),
|
||||
("private_case_slug", re.compile(r"user_REDACTED_YEAR|redacted_place", re.IGNORECASE)),
|
||||
(
|
||||
"private_birth_dict_tuple",
|
||||
re.compile(
|
||||
r"(?s)(?:year|--year|datetime\()\D*REDACTED_YEAR.{0,220}"
|
||||
r"(?:month|--month|,\s*)\D*4.{0,220}"
|
||||
r"(?:day|--day|,\s*)\D*17.{0,220}"
|
||||
r"(?:hour|--hour|,\s*)\D*14.{0,220}"
|
||||
r"(?:minute|--minute|,\s*)\D*49"
|
||||
),
|
||||
),
|
||||
]
|
||||
LOCAL_DENY_PATTERN_FILE = Path("scratch/local/public_release_deny_patterns.txt")
|
||||
ENV_DENY_PATTERNS = "PUBLIC_RELEASE_DENY_PATTERNS"
|
||||
|
||||
|
||||
def _is_skipped(path: Path) -> bool:
|
||||
rel = path.relative_to(ROOT).as_posix()
|
||||
def _literal_patterns(values: Iterable[str], prefix: str) -> list[tuple[str, re.Pattern[str]]]:
|
||||
patterns: list[tuple[str, re.Pattern[str]]] = []
|
||||
for index, value in enumerate(values, start=1):
|
||||
value = value.strip()
|
||||
if not value or value.startswith("#"):
|
||||
continue
|
||||
patterns.append((f"{prefix}_{index:02d}", re.compile(re.escape(value), re.IGNORECASE)))
|
||||
return patterns
|
||||
|
||||
|
||||
def deny_patterns(root: Path = ROOT) -> list[tuple[str, re.Pattern[str]]]:
|
||||
patterns = _literal_patterns(os.environ.get(ENV_DENY_PATTERNS, "").splitlines(), "private_env_pattern")
|
||||
local_file = root / LOCAL_DENY_PATTERN_FILE
|
||||
if local_file.is_file():
|
||||
patterns.extend(
|
||||
_literal_patterns(
|
||||
local_file.read_text(encoding="utf-8", errors="ignore").splitlines(),
|
||||
"private_local_pattern",
|
||||
)
|
||||
)
|
||||
return patterns
|
||||
|
||||
|
||||
def _is_skipped(path: Path, root: Path = ROOT) -> bool:
|
||||
try:
|
||||
rel = path.relative_to(root).as_posix()
|
||||
except ValueError:
|
||||
rel = path.as_posix()
|
||||
if rel in SKIP_FILES:
|
||||
return True
|
||||
return any(rel == item or rel.startswith(f"{item}/") for item in SKIP_DIRS)
|
||||
@@ -73,23 +84,30 @@ def iter_release_files(root: Path = ROOT) -> Iterable[Path]:
|
||||
cwd=root,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
check=False,
|
||||
)
|
||||
for line in completed.stdout.splitlines():
|
||||
path = root / line
|
||||
if not path.is_file() or _is_skipped(path):
|
||||
if completed.returncode == 0:
|
||||
candidates = (root / line for line in completed.stdout.splitlines())
|
||||
else:
|
||||
candidates = (path for path in root.rglob("*") if path.is_file())
|
||||
for path in candidates:
|
||||
if not path.is_file() or _is_skipped(path, root):
|
||||
continue
|
||||
if path.suffix.lower() in TEXT_SUFFIXES:
|
||||
yield path
|
||||
|
||||
|
||||
def scan_text(path: Path, text: str) -> list[dict[str, object]]:
|
||||
def scan_text(
|
||||
path: Path,
|
||||
text: str,
|
||||
patterns: Iterable[tuple[str, re.Pattern[str]]] | None = None,
|
||||
) -> list[dict[str, object]]:
|
||||
findings: list[dict[str, object]] = []
|
||||
try:
|
||||
display_path = path.relative_to(ROOT).as_posix()
|
||||
except ValueError:
|
||||
display_path = path.as_posix()
|
||||
for rule_id, pattern in DENY_PATTERNS:
|
||||
for rule_id, pattern in (patterns if patterns is not None else deny_patterns()):
|
||||
for match in pattern.finditer(text):
|
||||
line = text.count("\n", 0, match.start()) + 1
|
||||
findings.append(
|
||||
@@ -105,13 +123,14 @@ def scan_text(path: Path, text: str) -> list[dict[str, object]]:
|
||||
def build_report(root: Path = ROOT) -> dict[str, object]:
|
||||
findings: list[dict[str, object]] = []
|
||||
scanned = 0
|
||||
patterns = deny_patterns(root)
|
||||
for path in iter_release_files(root):
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except UnicodeDecodeError:
|
||||
text = path.read_text(encoding="utf-8", errors="ignore")
|
||||
scanned += 1
|
||||
findings.extend(scan_text(path, text))
|
||||
findings.extend(scan_text(path, text, patterns))
|
||||
return {
|
||||
"scope": "public_release_privacy_scan",
|
||||
"scanned_files": scanned,
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Validate structured real-case outcome replay manifests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
CASE_REQUIRED_FIELDS = {
|
||||
"case_id",
|
||||
"source",
|
||||
"chart_signature",
|
||||
"event_outcomes",
|
||||
"similarity",
|
||||
"replay",
|
||||
}
|
||||
SOURCE_REQUIRED_FIELDS = {"url", "source_grade", "license_or_quote_boundary"}
|
||||
EVENT_REQUIRED_FIELDS = {"event_type", "event_date", "outcome"}
|
||||
|
||||
|
||||
def _missing(mapping: dict[str, Any], required: set[str]) -> list[str]:
|
||||
return sorted(name for name in required if name not in mapping)
|
||||
|
||||
|
||||
def _case_errors(case: Any, index: int) -> list[dict[str, Any]]:
|
||||
if not isinstance(case, dict):
|
||||
return [{"case_index": index, "field": "case", "error": "not_object"}]
|
||||
|
||||
errors: list[dict[str, Any]] = []
|
||||
for field in _missing(case, CASE_REQUIRED_FIELDS):
|
||||
errors.append({"case_id": case.get("case_id"), "field": field, "error": "missing"})
|
||||
|
||||
source = case.get("source")
|
||||
if isinstance(source, dict):
|
||||
for field in _missing(source, SOURCE_REQUIRED_FIELDS):
|
||||
errors.append({"case_id": case.get("case_id"), "field": f"source.{field}", "error": "missing"})
|
||||
elif "source" in case:
|
||||
errors.append({"case_id": case.get("case_id"), "field": "source", "error": "not_object"})
|
||||
|
||||
events = case.get("event_outcomes")
|
||||
if isinstance(events, list):
|
||||
if not events:
|
||||
errors.append({"case_id": case.get("case_id"), "field": "event_outcomes", "error": "empty"})
|
||||
for event_index, event in enumerate(events):
|
||||
if not isinstance(event, dict):
|
||||
errors.append({"case_id": case.get("case_id"), "field": f"event_outcomes[{event_index}]", "error": "not_object"})
|
||||
continue
|
||||
for field in _missing(event, EVENT_REQUIRED_FIELDS):
|
||||
errors.append({"case_id": case.get("case_id"), "field": f"event_outcomes[{event_index}].{field}", "error": "missing"})
|
||||
elif "event_outcomes" in case:
|
||||
errors.append({"case_id": case.get("case_id"), "field": "event_outcomes", "error": "not_array"})
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def validate_manifest(path: str | Path) -> dict[str, Any]:
|
||||
manifest_path = Path(path)
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
cases = manifest.get("cases")
|
||||
if not isinstance(cases, list):
|
||||
return {
|
||||
"status": "blocked",
|
||||
"manifest_path": str(manifest_path),
|
||||
"case_count": 0,
|
||||
"replay_ready_count": 0,
|
||||
"blocked_reason": "cases_not_array",
|
||||
"errors": [{"field": "cases", "error": "not_array"}],
|
||||
}
|
||||
|
||||
errors: list[dict[str, Any]] = []
|
||||
replay_ready_count = 0
|
||||
for index, case in enumerate(cases):
|
||||
case_errors = _case_errors(case, index)
|
||||
errors.extend(case_errors)
|
||||
replay = case.get("replay") if isinstance(case, dict) else {}
|
||||
if not case_errors and isinstance(replay, dict) and replay.get("outcome_replay_status") == "replayed":
|
||||
replay_ready_count += 1
|
||||
|
||||
if errors:
|
||||
status = "invalid"
|
||||
blocked_reason = "case_contract_errors"
|
||||
elif not cases:
|
||||
status = "blocked"
|
||||
blocked_reason = manifest.get("blocked_reason") or "no_structured_outcome_replay_cases_imported"
|
||||
elif replay_ready_count == len(cases):
|
||||
status = "pass"
|
||||
blocked_reason = None
|
||||
else:
|
||||
status = "partial"
|
||||
blocked_reason = "some_cases_not_replayed"
|
||||
|
||||
return {
|
||||
"status": status,
|
||||
"manifest_path": str(manifest_path),
|
||||
"schema_version": manifest.get("schema_version"),
|
||||
"case_schema": manifest.get("case_schema"),
|
||||
"case_count": len(cases),
|
||||
"replay_ready_count": replay_ready_count,
|
||||
"blocked_reason": blocked_reason,
|
||||
"errors": errors,
|
||||
"runtime_boundary": manifest.get("runtime_boundary", ""),
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("manifest", nargs="?", default="references/real_case_calibration/replay_manifest.json")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(validate_manifest(args.manifest), ensure_ascii=False, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -24,11 +24,14 @@ SKIP_NAMES = {".env", ".env.local"}
|
||||
GENERATED_DOCS = {
|
||||
"INSTALL.md": """# Jyotish Skill Install
|
||||
|
||||
Basic Git version: https://github.com/732642856/yinduzhanxing
|
||||
|
||||
1. Unzip this package into a local folder.
|
||||
2. Run `python3 scripts/public_release_privacy_scan.py`.
|
||||
3. Run `python3 scripts/user_invocation_acceptance_check.py`.
|
||||
4. If the check passes, load this folder as a Codex skill/plugin or call `mcp_server.py`.
|
||||
5. VedAstro cloud evidence is optional. Without `official_raw_response`, reports must say `official_blocked` or `local_fallback`.
|
||||
4. Run `python3 scripts/skill_release_package.py --edition premium_cloud_drive`.
|
||||
5. If the checks pass, load this folder as a Codex skill/plugin or call `mcp_server.py`.
|
||||
6. VedAstro cloud evidence is optional. Without `official_raw_response`, reports must say `official_blocked` or `local_fallback`.
|
||||
|
||||
Do not add private birth data, API keys, or desktop oracle screenshots to this package.
|
||||
""",
|
||||
@@ -43,6 +46,11 @@ Do not add private birth data, API keys, or desktop oracle screenshots to this p
|
||||
|
||||
请使用 strict_workflow,并在输出中标明 VedAstro / PyJHora-JHora / jyotishganit / Real Case Calibration 的状态。
|
||||
如果没有 VedAstro official_raw_response,请标记 official_blocked 或 local_fallback。
|
||||
如果我提供西方占星导出,请作为 western_oracle_payload 进入统一主链,不要把单边西占信号说成双系统互证。
|
||||
|
||||
## Highest Quality Mode
|
||||
|
||||
请走高级版最高品质流程:direct_chart 或 rectification 入口 → evidence_packet → Technique Audit Table → VedAstro 三态 → 三引擎 parity replay 状态 → Real Case outcome replay 状态 → guided_topics。
|
||||
|
||||
## Birth-Time Rectification
|
||||
|
||||
@@ -50,6 +58,14 @@ Do not add private birth data, API keys, or desktop oracle screenshots to this p
|
||||
""",
|
||||
}
|
||||
|
||||
REQUIRED_CONTRACTS = [
|
||||
"references/real_case_calibration/replay_manifest.json",
|
||||
"references/oracle/three_engine_parity_replay_manifest.json",
|
||||
"references/oracle/western_oracle_adapter_contract.md",
|
||||
"scripts/user_invocation_acceptance_check.py",
|
||||
"scripts/diagnose_external_engine_adapters.py",
|
||||
]
|
||||
|
||||
|
||||
def _git_files() -> list[str]:
|
||||
completed = subprocess.run(["git", "ls-files", "-z"], cwd=ROOT, capture_output=True, check=True)
|
||||
@@ -70,15 +86,49 @@ def _edition_files(edition: str) -> list[str]:
|
||||
if edition not in manifest["editions"]:
|
||||
raise ValueError(f"unknown edition: {edition}")
|
||||
files = _git_files()
|
||||
files = sorted(set(files) | {path for path in REQUIRED_CONTRACTS if (ROOT / path).is_file()})
|
||||
if edition == "basic_git":
|
||||
keep = ("SKILL.md", "README.md", "mcp_server.py", ".codex-plugin/", "scripts/", "tests/")
|
||||
files = [path for path in files if path in keep or any(path.startswith(prefix) for prefix in keep if prefix.endswith("/"))]
|
||||
return [path for path in files if _allowed(path)]
|
||||
|
||||
|
||||
def _required_contracts() -> dict[str, bool]:
|
||||
return {path: (ROOT / path).is_file() for path in REQUIRED_CONTRACTS}
|
||||
|
||||
|
||||
def _package_acceptance(edition: str, privacy: dict[str, Any], files: list[str]) -> dict[str, Any]:
|
||||
contracts = _required_contracts()
|
||||
return {
|
||||
"scope": "skill_release_package_acceptance",
|
||||
"schema_version": 1,
|
||||
"edition": edition,
|
||||
"status": "pass" if privacy["status"] == "pass" and all(contracts.values()) else "blocked",
|
||||
"privacy_scan_status": privacy["status"],
|
||||
"file_count": len(files),
|
||||
"required_contracts": contracts,
|
||||
"acceptance_commands": release_manifest()["acceptance_commands"],
|
||||
"boundary": (
|
||||
"Package acceptance proves release hygiene and contracts only; it does not prove external oracle raw "
|
||||
"closure or same-chart parity until replay manifests contain imported rows."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _generated_docs(edition: str, privacy: dict[str, Any], files: list[str]) -> dict[str, str]:
|
||||
manifest = release_manifest()
|
||||
acceptance = _package_acceptance(edition, privacy, files)
|
||||
docs = dict(GENERATED_DOCS)
|
||||
docs["RELEASE_MANIFEST.json"] = json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
|
||||
docs["PACKAGE_ACCEPTANCE.json"] = json.dumps(acceptance, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
|
||||
return docs
|
||||
|
||||
|
||||
def build_package_plan(edition: str = "premium_cloud_drive") -> dict[str, Any]:
|
||||
privacy = privacy_scan()
|
||||
files = _edition_files(edition)
|
||||
generated_docs = _generated_docs(edition, privacy, files)
|
||||
acceptance = _package_acceptance(edition, privacy, files)
|
||||
return {
|
||||
"scope": "skill_release_package",
|
||||
"schema_version": 1,
|
||||
@@ -87,7 +137,9 @@ def build_package_plan(edition: str = "premium_cloud_drive") -> dict[str, Any]:
|
||||
"privacy_scan_status": privacy["status"],
|
||||
"file_count": len(files),
|
||||
"files": files,
|
||||
"generated_files": sorted(GENERATED_DOCS),
|
||||
"generated_files": sorted(generated_docs),
|
||||
"required_contracts": acceptance["required_contracts"],
|
||||
"package_acceptance_status": acceptance["status"],
|
||||
"boundary": "Dry-run plan only; use --write-zip to create a local zip, then upload manually if desired.",
|
||||
}
|
||||
|
||||
@@ -100,7 +152,8 @@ def write_zip(edition: str, output: Path) -> dict[str, Any]:
|
||||
with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED) as archive:
|
||||
for rel in plan["files"]:
|
||||
archive.write(ROOT / rel, rel)
|
||||
for rel, text in GENERATED_DOCS.items():
|
||||
privacy = privacy_scan()
|
||||
for rel, text in _generated_docs(edition, privacy, plan["files"]).items():
|
||||
archive.writestr(rel, text)
|
||||
return {**plan, "mode": "write_zip", "zip_path": str(output)}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Validate same-chart three-engine parity replay manifests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
REQUIRED_ENGINES = {"VedAstro", "PyJHora_JHora", "jyotishganit"}
|
||||
REQUIRED_ROW_FIELDS = {"section", "field", "local_value", "oracle_values", "status"}
|
||||
VALID_ROW_STATUSES = {"match", "mismatch", "blocked", "not_comparable"}
|
||||
|
||||
|
||||
def _row_errors(row: Any, index: int) -> list[dict[str, Any]]:
|
||||
if not isinstance(row, dict):
|
||||
return [{"row_index": index, "field": "row", "error": "not_object"}]
|
||||
errors: list[dict[str, Any]] = []
|
||||
for field in sorted(REQUIRED_ROW_FIELDS - set(row)):
|
||||
errors.append({"row_index": index, "field": field, "error": "missing"})
|
||||
if row.get("status") not in VALID_ROW_STATUSES:
|
||||
errors.append({"row_index": index, "field": "status", "error": "invalid"})
|
||||
if "oracle_values" in row and not isinstance(row.get("oracle_values"), dict):
|
||||
errors.append({"row_index": index, "field": "oracle_values", "error": "not_object"})
|
||||
return errors
|
||||
|
||||
|
||||
def validate_manifest(path: str | Path) -> dict[str, Any]:
|
||||
manifest_path = Path(path)
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
engines = manifest.get("engines") if isinstance(manifest.get("engines"), dict) else {}
|
||||
rows = manifest.get("comparison_rows")
|
||||
errors: list[dict[str, Any]] = []
|
||||
|
||||
missing_engines = sorted(REQUIRED_ENGINES - set(engines))
|
||||
for engine in missing_engines:
|
||||
errors.append({"field": f"engines.{engine}", "error": "missing"})
|
||||
|
||||
if not isinstance(rows, list):
|
||||
rows = []
|
||||
errors.append({"field": "comparison_rows", "error": "not_array"})
|
||||
|
||||
for index, row in enumerate(rows):
|
||||
errors.extend(_row_errors(row, index))
|
||||
|
||||
counts = {"match": 0, "mismatch": 0, "blocked": 0, "not_comparable": 0}
|
||||
for row in rows:
|
||||
if isinstance(row, dict) and row.get("status") in counts:
|
||||
counts[row["status"]] += 1
|
||||
|
||||
if errors:
|
||||
status = "invalid"
|
||||
blocked_reason = "parity_manifest_contract_errors"
|
||||
elif not rows:
|
||||
status = "blocked"
|
||||
blocked_reason = manifest.get("blocked_reason") or "no_same_chart_oracle_rows_imported"
|
||||
elif counts["mismatch"]:
|
||||
status = "mismatch"
|
||||
blocked_reason = None
|
||||
elif counts["blocked"]:
|
||||
status = "partial"
|
||||
blocked_reason = "some_comparison_rows_blocked"
|
||||
else:
|
||||
status = "pass"
|
||||
blocked_reason = None
|
||||
|
||||
return {
|
||||
"status": status,
|
||||
"tested": status in {"pass", "mismatch", "partial"},
|
||||
"manifest_path": str(manifest_path),
|
||||
"case_id": manifest.get("case_id"),
|
||||
"birth_data_policy": manifest.get("birth_data_policy"),
|
||||
"engine_count": len(engines),
|
||||
"required_engines": sorted(REQUIRED_ENGINES),
|
||||
"missing_engines": missing_engines,
|
||||
"comparison_row_count": len(rows),
|
||||
"match_count": counts["match"],
|
||||
"mismatch_count": counts["mismatch"],
|
||||
"blocked_row_count": counts["blocked"],
|
||||
"not_comparable_count": counts["not_comparable"],
|
||||
"blocked_reason": blocked_reason,
|
||||
"errors": errors,
|
||||
"runtime_boundary": manifest.get("runtime_boundary", ""),
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("manifest", nargs="?", default="references/oracle/three_engine_parity_replay_manifest.json")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(validate_manifest(args.manifest), ensure_ascii=False, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -18,11 +18,21 @@ try:
|
||||
except Exception: # pragma: no cover - import path varies in tests/CLI
|
||||
from scripts.diagnose_jyotishganit_adapter import build_report as build_jyotishganit_adapter_report
|
||||
|
||||
try:
|
||||
from cross_system_arbitrator import build_cross_system_arbitration
|
||||
except Exception: # pragma: no cover - import path varies in tests/CLI
|
||||
from scripts.cross_system_arbitrator import build_cross_system_arbitration
|
||||
|
||||
try:
|
||||
from functional_benefics import derive_functional_benefic_malefic
|
||||
except Exception: # pragma: no cover - import path varies in tests/CLI
|
||||
from scripts.functional_benefics import derive_functional_benefic_malefic
|
||||
|
||||
try:
|
||||
from real_case_replay_validator import validate_manifest as validate_real_case_replay_manifest
|
||||
except Exception: # pragma: no cover - import path varies in tests/CLI
|
||||
from scripts.real_case_replay_validator import validate_manifest as validate_real_case_replay_manifest
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RouteDefinition:
|
||||
@@ -426,12 +436,16 @@ class UnifiedConsultationOrchestrator:
|
||||
),
|
||||
}
|
||||
missing = [name for name, section in sections.items() if section.get("status") == "missing"]
|
||||
signals = chart_data.get("cross_system_signals")
|
||||
if not isinstance(signals, list):
|
||||
signals = modules.get("cross_system_signals") if isinstance(modules.get("cross_system_signals"), list) else []
|
||||
return {
|
||||
"status": "complete" if not missing else "partial",
|
||||
"route": dict(route_packet),
|
||||
"required_sections": list(self.EVIDENCE_PACKET_REQUIRED_SECTIONS),
|
||||
"sections": sections,
|
||||
"functional_benefic_malefic": functional_layer,
|
||||
"signals": [item for item in signals if isinstance(item, dict)],
|
||||
"missing_sections": missing,
|
||||
}
|
||||
|
||||
@@ -463,6 +477,8 @@ class UnifiedConsultationOrchestrator:
|
||||
"event_trigger_keywords": ["UL", "Darapada", "7th lord", "DK"],
|
||||
},
|
||||
}
|
||||
replay_manifest_path = Path(__file__).resolve().parents[1] / "references/real_case_calibration/replay_manifest.json"
|
||||
replay_manifest = validate_real_case_replay_manifest(replay_manifest_path)
|
||||
candidate_refs = case_index_by_domain.get(route, [])
|
||||
packet = machine_evidence_packet if isinstance(machine_evidence_packet, dict) else {}
|
||||
sections = packet.get("sections") if isinstance(packet.get("sections"), dict) else {}
|
||||
@@ -518,6 +534,8 @@ class UnifiedConsultationOrchestrator:
|
||||
"route": route,
|
||||
"source_roots": ["references/real_case_studies", "docs/benchmark"],
|
||||
"case_index_by_domain": case_index_by_domain,
|
||||
"required_replay_schema": "references/real_case_calibration/catalog.schema.json",
|
||||
"outcome_replay_manifest": replay_manifest,
|
||||
"candidate_refs": list(candidate_refs),
|
||||
"scored_candidates": scored_candidates,
|
||||
"reference_grade": scored_candidates[0]["reference_grade"] if scored_candidates else "ungraded_until_similarity_scored",
|
||||
@@ -539,6 +557,7 @@ class UnifiedConsultationOrchestrator:
|
||||
interpretation_source_runtime_coverage: dict[str, Any] | None = None,
|
||||
machine_evidence_packet: dict[str, Any] | None = None,
|
||||
real_case_calibration: dict[str, Any] | None = None,
|
||||
western_evidence_packet: dict[str, Any] | None = None,
|
||||
blind: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
official = vedastro_official if isinstance(vedastro_official, dict) else {}
|
||||
@@ -575,6 +594,13 @@ class UnifiedConsultationOrchestrator:
|
||||
blocked_items.append("real_case_calibration_not_yet_materialized")
|
||||
elif case_status != "complete":
|
||||
blocked_items.append("real_case_calibration_partial")
|
||||
cross_system_arbitration = build_cross_system_arbitration(
|
||||
route_packet=route_packet,
|
||||
jyotish_evidence=packet,
|
||||
western_evidence=western_evidence_packet,
|
||||
)
|
||||
if cross_system_arbitration["status"] != "used":
|
||||
blocked_items.append("cross_system_arbitration_not_complete")
|
||||
technique_audit_table = [
|
||||
{
|
||||
"technique": "VedAstro Cloud State",
|
||||
@@ -606,6 +632,7 @@ class UnifiedConsultationOrchestrator:
|
||||
else "claims_capped_until_pyjhora_jhora_jyotishganit_are_invoked_for_this_run"
|
||||
),
|
||||
},
|
||||
*cross_system_arbitration["technique_audit_rows"],
|
||||
{
|
||||
"technique": "Evidence Packet",
|
||||
"status": packet_status,
|
||||
@@ -653,6 +680,7 @@ class UnifiedConsultationOrchestrator:
|
||||
"vedastro_cloud_state": vedastro_state,
|
||||
"vedastro_runtime_truth": dict(runtime_truth),
|
||||
"external_engine_cross_validation": external_cross_validation,
|
||||
"cross_system_arbitration": cross_system_arbitration,
|
||||
"source_priority": {
|
||||
"mode": self.SOURCE_PRIORITY["mode"],
|
||||
"priority": list(self.SOURCE_PRIORITY["priority"]),
|
||||
@@ -691,7 +719,10 @@ class UnifiedConsultationOrchestrator:
|
||||
"technique_audit_table": technique_audit_table,
|
||||
"required_rows": [
|
||||
"VedAstro Cloud State",
|
||||
"VedAstro Raw Archive Manifest",
|
||||
"External Engine Cross-Validation",
|
||||
"Western Cross-Validation",
|
||||
"Cross-System Arbitration",
|
||||
"Evidence Packet",
|
||||
"Blind Technical Mode",
|
||||
"MEVG / Global Web Evidence",
|
||||
|
||||
@@ -158,7 +158,8 @@ def main() -> int:
|
||||
if cursor in (None, [], ""):
|
||||
errors.append(f"missing_entrypoint_{'_'.join(path)}")
|
||||
|
||||
if adapter_report.get("status") not in {"pass", "partial"}:
|
||||
usable_adapter_statuses = {"pass", "partial", "complete"}
|
||||
if adapter_report.get("status") not in usable_adapter_statuses:
|
||||
errors.append("external_adapter_status_unusable")
|
||||
|
||||
result = {
|
||||
@@ -168,7 +169,7 @@ def main() -> int:
|
||||
"checks": {
|
||||
"user_invocation_tests": pytest_check["returncode"] == 0,
|
||||
"guided_topics_entrypoint": entrypoint["returncode"] == 0 and not any(e.startswith("missing_entrypoint_") for e in errors),
|
||||
"external_adapter_diagnostics": adapter_report.get("status") in {"pass", "partial"},
|
||||
"external_adapter_diagnostics": adapter_report.get("status") in usable_adapter_statuses,
|
||||
},
|
||||
"entrypoint_runtime_mode": _runtime_summary(entrypoint_report),
|
||||
"entrypoint_strict_workflow": _strict_summary(entrypoint_report),
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Western astrology evidence packet contract."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
REQUIRED_SECTIONS = [
|
||||
"natal",
|
||||
"timing_techniques",
|
||||
"signals",
|
||||
]
|
||||
|
||||
|
||||
def _section(value: Any, source_path: str) -> dict[str, Any]:
|
||||
present = bool(value)
|
||||
return {
|
||||
"status": "used" if present else "missing",
|
||||
"source_path": source_path,
|
||||
}
|
||||
|
||||
|
||||
def _normalize_signals(signals: list[dict[str, Any]] | None) -> list[dict[str, Any]]:
|
||||
normalized: list[dict[str, Any]] = []
|
||||
for item in signals or []:
|
||||
if not isinstance(item, dict) or not item.get("theme") or not item.get("claim"):
|
||||
continue
|
||||
normalized.append(
|
||||
{
|
||||
"theme": str(item.get("theme")),
|
||||
"claim": str(item.get("claim")),
|
||||
"timing": str(item.get("timing") or "unspecified"),
|
||||
"source": str(item.get("source") or "western_evidence_packet"),
|
||||
}
|
||||
)
|
||||
return normalized
|
||||
|
||||
|
||||
def build_western_evidence_packet(
|
||||
*,
|
||||
route_packet: dict[str, Any],
|
||||
natal: dict[str, Any] | None,
|
||||
timing_techniques: dict[str, Any] | None,
|
||||
signals: list[dict[str, Any]] | None,
|
||||
) -> dict[str, Any]:
|
||||
"""Materialize a small auditable Western evidence packet."""
|
||||
|
||||
normalized_signals = _normalize_signals(signals)
|
||||
sections = {
|
||||
"natal": _section(natal, "western.natal"),
|
||||
"timing_techniques": _section(timing_techniques, "western.timing_techniques"),
|
||||
"signals": _section(normalized_signals, "western.signals"),
|
||||
}
|
||||
missing = [name for name in REQUIRED_SECTIONS if sections[name]["status"] == "missing"]
|
||||
return {
|
||||
"system": "western_astrology",
|
||||
"status": "complete" if not missing else "partial",
|
||||
"route": dict(route_packet),
|
||||
"required_sections": list(REQUIRED_SECTIONS),
|
||||
"sections": sections,
|
||||
"natal": natal or {},
|
||||
"timing_techniques": timing_techniques or {},
|
||||
"signals": normalized_signals,
|
||||
"missing_sections": missing,
|
||||
"license_boundary": (
|
||||
"This packet stores derived evidence only. Kerykeion, pyswisseph, Flatlib, Immanuel, or desktop "
|
||||
"software outputs must remain external oracle inputs unless their licenses permit bundling."
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Normalize external Western astrology oracle JSON into a western evidence packet."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from western_evidence_packet import build_western_evidence_packet
|
||||
except Exception: # pragma: no cover - import path varies in tests/CLI
|
||||
from scripts.western_evidence_packet import build_western_evidence_packet
|
||||
|
||||
|
||||
_ASPECT_ALIASES = {
|
||||
"conj": "conjunction",
|
||||
"conjunction": "conjunction",
|
||||
"trine": "trine",
|
||||
"sextile": "sextile",
|
||||
"square": "square",
|
||||
"opposition": "opposition",
|
||||
"opp": "opposition",
|
||||
}
|
||||
|
||||
|
||||
def _norm(value: Any) -> str:
|
||||
return str(value or "").strip().lower().replace("-", "_").replace(" ", "_")
|
||||
|
||||
|
||||
def _aspect_label(item: dict[str, Any]) -> tuple[str, str, str]:
|
||||
planet = _norm(item.get("planet") or item.get("transit_planet"))
|
||||
aspect = _ASPECT_ALIASES.get(_norm(item.get("aspect")), _norm(item.get("aspect")))
|
||||
target = _norm(item.get("target") or item.get("point") or item.get("natal_point"))
|
||||
return planet, aspect, target
|
||||
|
||||
|
||||
def _timing(item: dict[str, Any]) -> str:
|
||||
return str(item.get("timing") or item.get("date") or item.get("window") or "unspecified")
|
||||
|
||||
|
||||
def _signals_from_aspects(aspects: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
signals: list[dict[str, Any]] = []
|
||||
for item in aspects:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
planet, aspect, target = _aspect_label(item)
|
||||
timing = _timing(item)
|
||||
source = str(item.get("source") or f"{planet} {aspect} {target}").replace("_", " ")
|
||||
if planet == "uranus" and aspect == "conjunction" and target in {"mc", "midheaven"}:
|
||||
signals.append(
|
||||
{
|
||||
"theme": "career_relocation",
|
||||
"claim": "career_triggered_relocation",
|
||||
"timing": timing,
|
||||
"source": source,
|
||||
}
|
||||
)
|
||||
if planet == "jupiter" and aspect in {"trine", "sextile"} and target in {"mercury", "venus", "mc", "midheaven"}:
|
||||
signals.append(
|
||||
{
|
||||
"theme": "career",
|
||||
"claim": "client_cooperation_opportunity",
|
||||
"timing": timing,
|
||||
"source": source,
|
||||
}
|
||||
)
|
||||
if planet == "saturn" and aspect == "conjunction" and target in {"sun", "mercury", "venus"}:
|
||||
signals.append(
|
||||
{
|
||||
"theme": "career",
|
||||
"claim": "career_responsibility_test",
|
||||
"timing": timing,
|
||||
"source": source,
|
||||
}
|
||||
)
|
||||
return signals
|
||||
|
||||
|
||||
def build_packet_from_oracle_payload(
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
route_packet: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Build a standard Western packet from an external-oracle JSON payload."""
|
||||
|
||||
explicit_signals = payload.get("signals") if isinstance(payload.get("signals"), list) else []
|
||||
aspects = payload.get("aspects") if isinstance(payload.get("aspects"), list) else []
|
||||
signals = list(explicit_signals) or _signals_from_aspects(aspects)
|
||||
timing_techniques = payload.get("timing_techniques")
|
||||
if not isinstance(timing_techniques, dict):
|
||||
timing_techniques = {}
|
||||
if aspects and "aspects" not in timing_techniques:
|
||||
timing_techniques = {**timing_techniques, "aspects": aspects}
|
||||
packet = build_western_evidence_packet(
|
||||
route_packet=route_packet,
|
||||
natal=payload.get("natal") if isinstance(payload.get("natal"), dict) else {},
|
||||
timing_techniques=timing_techniques,
|
||||
signals=signals,
|
||||
)
|
||||
packet["source_engine"] = str(payload.get("source_engine") or payload.get("engine") or "external_western_oracle_json")
|
||||
packet["adapter_boundary"] = {
|
||||
"bundles_external_code": False,
|
||||
"accepted_input": "external_json_export",
|
||||
"role": "western_cross_validation_evidence",
|
||||
}
|
||||
return packet
|
||||
|
||||
|
||||
def _parse_args(argv: list[str]) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--input", required=True, help="Path to external Western oracle JSON export.")
|
||||
parser.add_argument("--theme", default="general", help="Primary theme, e.g. career/marriage/wealth.")
|
||||
parser.add_argument("--question-type", default=None, help="Question type override.")
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = _parse_args(list(argv or sys.argv[1:]))
|
||||
payload = json.loads(Path(args.input).read_text(encoding="utf-8"))
|
||||
route_packet = {
|
||||
"question_type": args.question_type or args.theme,
|
||||
"primary_theme": args.theme,
|
||||
}
|
||||
packet = build_packet_from_oracle_payload(payload, route_packet=route_packet)
|
||||
print(json.dumps(packet, ensure_ascii=False, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user