Files
Jyotisha/scripts/vedastro_user_entrypoint.py
T
2026-07-02 23:17:47 +08:00

280 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""User-level VedAstro + strict-workflow entrypoint for Codex sessions."""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any
try:
from scripts.diagnose_vedastro_mode import build_report as build_runtime_mode_report
from scripts.local_env import load_local_env
except ModuleNotFoundError: # pragma: no cover - direct script execution
from diagnose_vedastro_mode import build_report as build_runtime_mode_report
from local_env import load_local_env
ROOT = Path(__file__).resolve().parents[1]
RUNNER = ROOT / "scripts" / "vedastro_official_capability_runner.py"
def _bool_env(name: str) -> bool:
return os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"}
def _int_env(name: str, default: int = 0) -> int:
raw = os.environ.get(name, "").strip()
if not raw:
return default
try:
return int(float(raw))
except ValueError:
return default
def _themes(raw: str) -> list[str]:
aliases = {
"relationship": "marriage",
"relationships": "marriage",
"finance": "wealth",
"money": "wealth",
"事业": "career",
"婚恋": "marriage",
"婚姻": "marriage",
"财富": "wealth",
"健康": "health",
"教育": "education",
"房产": "property",
"子女": "children",
"迁移": "migration",
"问卜": "prashna",
"校时": "rectification",
"应期": "timing",
}
values: list[str] = []
for item in raw.replace("", ",").split(","):
key = aliases.get(item.strip().lower(), item.strip().lower())
if key and key not in values:
values.append(key)
return values or ["career", "marriage", "wealth"]
def _primary_route(question: str, themes: list[str]) -> str:
text = f"{question} {' '.join(themes)}".lower()
route_aliases = [
("career", ("career", "job", "work", "profession", "事业", "工作", "职业", "项目")),
("relationship", ("marriage", "relationship", "spouse", "partner", "婚恋", "婚姻", "伴侣")),
("finance", ("wealth", "finance", "money", "income", "财富", "金钱", "收入")),
]
for route, tokens in route_aliases:
if any(token in text for token in tokens):
return route
return "general"
def _case_from_args(args: argparse.Namespace, themes: list[str]) -> dict[str, Any]:
return {
"year": args.year,
"month": args.month,
"day": args.day,
"hour": args.hour,
"minute": args.minute,
"second": args.second,
"lat": args.lat,
"lon": args.lon,
"tz": args.tz,
"reference_date": args.reference_date,
"themes": themes,
"ayanamsa_policy": args.ayanamsa,
"node_policy": args.node_mode,
}
def _run_capability_catalog(case: dict[str, Any]) -> dict[str, Any]:
completed = subprocess.run(
[
sys.executable,
str(RUNNER),
"--bundle",
"official_full_capability_catalog",
"--birth-json",
json.dumps(case, ensure_ascii=False),
],
cwd=ROOT,
text=True,
capture_output=True,
timeout=max(5.0, float(os.environ.get("VEDASTRO_TIMEOUT_SECONDS", "5") or 5)),
check=False,
env=os.environ.copy(),
)
if completed.returncode != 0:
return {
"available": False,
"status": "official_full_capability_catalog_runtime_error",
"summary": {},
"domain_routing": {},
"dynamic_selection": {},
"stderr": (completed.stderr or "").strip(),
}
try:
return json.loads(completed.stdout)
except json.JSONDecodeError:
return {
"available": False,
"status": "official_full_capability_catalog_invalid_json",
"summary": {},
"domain_routing": {},
"dynamic_selection": {},
"stdout_excerpt": (completed.stdout or "").strip()[:500],
}
def _strict_workflow_summary(route: str, catalog: dict[str, Any]) -> dict[str, Any]:
dynamic_selection = catalog.get("dynamic_selection") if isinstance(catalog.get("dynamic_selection"), dict) else {}
theme_for_route = {"relationship": "marriage", "finance": "wealth"}.get(route, route)
return {
"triggered": route in {"career", "relationship", "finance"},
"primary_route": route,
"routes_available": ["career", "relationship", "finance"],
"source": "strict_workflow_contract_summary",
"official_capability_selection": dynamic_selection.get(theme_for_route) or {},
"boundary": (
"This entrypoint triggers the strict workflow contract lane and passes VedAstro official catalog "
"selection metadata; it does not claim every official method was executed."
),
}
def _cache_and_queue_report() -> dict[str, Any]:
return {
"official_full_snapshot_cache_scope": "official_full_snapshot_semantic_cache",
"official_full_snapshot_cache_ttl_seconds": _int_env("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", 0),
"range_scan_cache_scope": "vedastro_range_scan_request_cache",
"range_scan_cache_ttl_seconds": _int_env("VEDASTRO_CACHE_TTL_SECONDS", 0),
"free_tier_queue_enabled": _bool_env("VEDASTRO_FREE_TIER_QUEUE")
or _bool_env("VEDASTRO_FREE_TIER_QUEUE_ENABLED")
or _bool_env("VEDASTRO_ENABLE_FREE_TIER_QUEUE"),
"sample_limit": _int_env("VEDASTRO_FULL_CATALOG_SAMPLE_LIMIT", 0),
"artifact_root": "scratch/local/vedastro_adapter",
}
def build_report(args: argparse.Namespace) -> dict[str, Any]:
load_local_env(ROOT)
themes = _themes(args.themes)
case = _case_from_args(args, themes)
runtime = build_runtime_mode_report()
catalog = _run_capability_catalog(case)
route = _primary_route(args.question, themes)
return {
"scope": "vedastro_user_entrypoint",
"schema_version": 1,
"input": {
"question": args.question,
"themes": themes,
"birth": {key: case[key] for key in ("year", "month", "day", "hour", "minute", "second", "lat", "lon", "tz")},
"reference_date": args.reference_date,
},
"runtime_mode": runtime,
"official_capability_catalog": {
"status": catalog.get("status") or "blocked",
"available": bool(catalog.get("available")),
"summary": catalog.get("summary") or {},
"coverage": catalog.get("coverage") or {},
"domain_routing": catalog.get("domain_routing") or {},
"dynamic_selection": catalog.get("dynamic_selection") or {},
},
"cache_and_queue": _cache_and_queue_report(),
"strict_workflow": _strict_workflow_summary(route, catalog),
"honesty_boundary": {
"all_641_methods_executed": False,
"official_catalog_classified": bool(catalog.get("summary")),
"official_extended_ready": bool(runtime.get("official_ready")),
"rule": (
"Use official catalog domain routing and dynamic selection as evidence metadata. "
"Methods requiring user context/text/rectification remain blocked or secondary until inputs exist."
),
},
"user_commands": {
"json": (
"python3 scripts/vedastro_user_entrypoint.py --year YYYY --month MM --day DD "
"--hour HH --minute MM --lat LAT --lon LON --tz TZ --question '...' "
"--themes career,marriage,wealth --reference-date YYYY-MM-DD --format json "
"# includes official_full_capability_catalog"
),
"markdown": (
"python3 scripts/vedastro_user_entrypoint.py --year YYYY --month MM --day DD "
"--hour HH --minute MM --lat LAT --lon LON --tz TZ --question '...' --format markdown"
),
},
}
def render_markdown(report: dict[str, Any]) -> str:
runtime = report["runtime_mode"]
catalog = report["official_capability_catalog"]
cache = report["cache_and_queue"]
strict = report["strict_workflow"]
summary = catalog.get("summary") or {}
lines = [
"# VedAstro 用户级入口",
"",
f"- runtime_mode: `{runtime.get('mode')}`",
f"- official_ready: `{str(runtime.get('official_ready')).lower()}`",
f"- catalog_status: `{catalog.get('status')}`",
f"- catalog_method_count: `{summary.get('catalog_method_count', 0)}`",
f"- unknown_method_count: `{summary.get('unknown_method_count', 0)}`",
f"- misrouted_general_method_count: `{summary.get('misrouted_general_method_count', 0)}`",
f"- strict workflow triggered: `{str(strict.get('triggered')).lower()}`",
f"- primary_route: `{strict.get('primary_route')}`",
"",
"## Cache / Free-Tier Queue",
"",
f"- official_full_snapshot_cache_ttl_seconds: `{cache['official_full_snapshot_cache_ttl_seconds']}`",
f"- range_scan_cache_ttl_seconds: `{cache['range_scan_cache_ttl_seconds']}`",
f"- free_tier_queue_enabled: `{str(cache['free_tier_queue_enabled']).lower()}`",
"",
"## Boundary",
"",
"- 这个入口会启动 VedAstro official capability catalog 分类、动态主题选择和 strict workflow 合同摘要。",
"- 它不会把 641 项全部当作已执行;需要用户上下文、文本问题或校时画像的方法会保持 blocked/secondary。",
"- 如果 runtime 是 `fast_local_fallback`,解盘必须诚实降级,不得声称 official extended 已闭环。",
]
return "\n".join(lines) + "\n"
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--year", type=int, required=True)
parser.add_argument("--month", type=int, required=True)
parser.add_argument("--day", type=int, required=True)
parser.add_argument("--hour", type=int, required=True)
parser.add_argument("--minute", type=int, required=True)
parser.add_argument("--second", type=int, default=0)
parser.add_argument("--lat", type=float, required=True)
parser.add_argument("--lon", type=float, required=True)
parser.add_argument("--tz", type=float, required=True)
parser.add_argument("--question", default="")
parser.add_argument("--themes", default="career,marriage,wealth")
parser.add_argument("--reference-date", required=True)
parser.add_argument("--ayanamsa", default="lahiri")
parser.add_argument("--node-mode", default="mean")
parser.add_argument("--format", choices=["json", "markdown"], default="markdown")
args = parser.parse_args()
report = build_report(args)
if args.format == "json":
print(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True))
else:
print(render_markdown(report), end="")
return 0
if __name__ == "__main__":
raise SystemExit(main())