Add VedAstro user entrypoint
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user