Wire VedAstro orchestrator into strict workflows

This commit is contained in:
732642856
2026-06-29 10:26:54 +08:00
parent 3452beee1b
commit 0f9b06d9d4
18 changed files with 904 additions and 162 deletions
@@ -544,6 +544,24 @@ def test_relationship_dignity_guardrail_uses_relevant_planets_only() -> None:
assert "dignity_supportive_recovery" in strict["event_judgement"]["secondary_context"]
def test_relationship_dignity_guardrail_treats_great_friend_on_relevant_planet_as_supportive_context() -> None:
result = _base_relationship_result()
result["modules"]["chart"] = {
"ascendant": {"sign": "Leo"},
"planets": {
"Venus": {"status": "极友(Great Friend)"},
"Jupiter": {"status": "中性(Neutral)"},
"Saturn": {"status": "中性(Neutral)"},
},
}
strict = _collect_strict_evidence("relationship", result)
assert strict["present_evidence"]["dignity_guardrail"]["status"] == "caution"
assert strict["present_evidence"]["dignity_guardrail"]["score_delta"] == 3
assert "dignity_supportive_friendship" in strict["event_judgement"]["secondary_context"]
def test_relationship_caps_confidence_when_provided_shadbala_components_are_incomplete() -> None:
result = _base_relationship_result()
result["modules"]["dasa_convergence"]["domain_activations"]["marriage_partnership"] = {
@@ -0,0 +1,57 @@
from __future__ import annotations
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_vedastro_evidence_orchestrator_routes_to_minimal_domain_set(monkeypatch) -> None:
from scripts import vedastro_evidence_orchestrator as orchestrator
calls = []
def fake_scan(case, domain, start_date, end_date, case_id):
calls.append((domain, start_date, end_date, case_id, case["year"]))
return {
"backend": "vedastro_service_adapter_candidate",
"available": True,
"status": "ok",
"operation": "range_scan",
"domain": domain,
"event_count": 1,
"evidence_ledger": [{"domain": domain, "event_id": f"{domain}_event"}],
"source_metadata": {"endpoint_host": "api.vedastro.org"},
}
monkeypatch.setattr(orchestrator, "run_range_scan_for_case", fake_scan)
result = orchestrator.orchestrate_vedastro_evidence(
{
"year": REDACTED_YEAR,
"month": 4,
"day": 17,
"hour": 14,
"minute": 49,
"lat": 36.42,
"lon": 114.2,
"tz": 8.0,
},
route="relationship",
reference_date="2026-06-29",
)
assert [call[0] for call in calls] == ["marriage"]
assert result["source_metadata"]["auto_ingested_by"] == "VedAstroEvidenceOrchestrator"
assert result["source_metadata"]["node_coverage"]["strategy"] == "domain_scoped_range_scan"
assert result["event_count"] == 1
def test_api_and_mcp_use_shared_vedastro_evidence_orchestrator() -> None:
api = (ROOT / "scripts" / "jyotish_api_server.py").read_text(encoding="utf-8")
mcp = (ROOT / "mcp_server.py").read_text(encoding="utf-8")
assert "vedastro_evidence_orchestrator" in api
assert "orchestrate_vedastro_evidence" in api
assert "vedastro_evidence_orchestrator" in mcp
assert "orchestrate_vedastro_evidence" in mcp
@@ -9,7 +9,9 @@ import subprocess
import sys
from copy import deepcopy
from pathlib import Path
from unittest import mock
import mcp_server
from mcp_server import _collect_strict_evidence
@@ -44,7 +46,8 @@ def test_vedastro_adapter_declares_external_technique_evidence_policy() -> None:
def test_vedastro_adapter_builds_external_technique_preview_without_network() -> None:
env = os.environ.copy()
env["VEDASTRO_API_ENDPOINT"] = "https://example.invalid/vedastro"
env.pop("VEDASTRO_ENABLE_NETWORK", None)
env["VEDASTRO_ENABLE_NETWORK"] = "0"
env["JYOTISH_SKIP_LOCAL_ENV"] = "1"
completed = subprocess.run(
[
@@ -228,3 +231,114 @@ def test_strict_workflow_marks_vedastro_range_scan_used_without_score_or_label_o
assert strict["event_judgement"]["score"] == base["event_judgement"]["score"]
assert strict["event_judgement"]["dominant_label"] == base["event_judgement"]["dominant_label"]
assert strict["event_judgement"]["payout_label"] == base["event_judgement"]["payout_label"]
def test_strict_workflow_auto_ingests_live_vedastro_range_scan_for_finance_route() -> None:
fake_range_scan = {
"backend": "vedastro_service_adapter_candidate",
"available": True,
"status": "ok",
"operation": "range_scan",
"domain": "wealth",
"event_count": 1,
"evidence_ledger": [
{
"source": "vedastro_service_adapter_candidate",
"operation": "range_scan",
"domain": "wealth",
"event_id": "GocharJupiterIn11th",
"signal_label": "Jupiter in 11th gains window",
"score": 76,
"tags": ["wealth", "transit"],
}
],
"source_metadata": {"sampling_mode": "at_time_sweep"},
}
with mock.patch.object(mcp_server, "_run_engine") as run_engine, mock.patch.object(
mcp_server,
"_maybe_attach_vedastro_evidence",
) as attach_vedastro:
run_engine.return_value = {"modules": _finance_modules()}
attach_vedastro.return_value = {
"modules": {
**_finance_modules(),
"vedastro_range_scan_result": fake_range_scan,
}
}
result = mcp_server.strict_workflow(
question="今年的财富机会怎么样?",
year=REDACTED_YEAR,
month=4,
day=17,
hour=14,
minute=49,
lat=36.42,
lon=114.2,
tz=8.0,
age=33,
transit_date="2026-06-29",
node_mode="mean",
)
strict = result["strict_workflow"]
attach_vedastro.assert_called_once()
assert strict["present_evidence"]["external_activation"]["level"] == "moderate"
assert "external_activation_support" in strict["event_judgement"]["secondary_context"]
assert "vedastro_range_scan_missing" not in strict["event_judgement"]["secondary_context"]
def test_strict_workflow_auto_ingests_live_vedastro_external_technique_as_context_only() -> None:
fake_external_technique = {
"backend": "vedastro_service_adapter_candidate",
"available": True,
"status": "ok",
"operation": "calculation_method",
"role": "external_technique_evidence",
"domain": "wealth",
"evidence_ledger": [
{
"source": "vedastro_service_adapter_candidate",
"operation": "calculation_method",
"role": "external_technique_evidence",
"domain": "wealth",
"method": "CalculateShadbala",
"api_endpoint": "Calculate/Shadbala",
"status": "ok",
"summary": "VedAstro Shadbala node returned as external evidence.",
}
],
}
with mock.patch.object(mcp_server, "_run_engine") as run_engine, mock.patch.object(
mcp_server,
"_maybe_attach_vedastro_evidence",
) as attach_vedastro:
run_engine.return_value = {"modules": _finance_modules()}
attach_vedastro.return_value = {
"modules": {
**_finance_modules(),
"external_technique_evidence": fake_external_technique,
}
}
result = mcp_server.strict_workflow(
question="我的财务今年如何?",
year=REDACTED_YEAR,
month=4,
day=17,
hour=14,
minute=49,
lat=36.42,
lon=114.2,
tz=8.0,
age=33,
transit_date="2026-06-29",
node_mode="mean",
)
strict = result["strict_workflow"]
assert strict["present_evidence"]["external_technique_evidence"]["level"] == "context_only"
assert strict["present_evidence"]["external_technique_evidence"]["methods"] == ["CalculateShadbala"]
assert "external_technique_evidence" in strict["event_judgement"]["secondary_context"]
+72
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
import json
from pathlib import Path
from unittest import mock
from scripts import vedastro_service_adapter
@@ -73,3 +74,74 @@ def test_replay_stats_track_zero_event_domain_and_rejected_alias_noise() -> None
assert replay["zero_event_domains"] == ["wealth"]
assert replay["match_counts"]["rejected"] == 1
assert replay["recommended_allowlist_candidates"] == []
def test_normalize_official_live_payload_supports_nested_searchevents_and_tag_string() -> None:
payload = {
"Status": "Pass",
"Payload": {
"SearchEvents": [
{
"Name": "BadLunarMonthForBuilding",
"Nature": "Bad",
"Description": "Building work should be avoided in this lunar month.",
"StartTime": {"StdTime": "12:00 01/01/2026 +08:00"},
"EndTime": {"StdTime": "12:00 01/01/2026 +08:00"},
"Tag": "Building,General",
}
]
},
}
report = vedastro_service_adapter._normalize_range_scan_success( # noqa: SLF001
payload,
"https://api.vedastro.org/api",
_request_preview("career"),
)
assert report["event_count"] == 1
assert report["evidence_ledger"][0]["event_id"] == "BadLunarMonthForBuilding"
assert report["evidence_ledger"][0]["matched_by"] == "official_tag"
assert report["evidence_ledger"][0]["matched_terms"] == ["Building"]
assert report["evidence_ledger"][0]["tags"] == ["Building", "General"]
def test_run_range_scan_case_refreshes_live_sampling_request_profile_for_each_sample_date() -> None:
case = vedastro_service_adapter.PARITY_CASES["beijing_first_use_demo"]
captured_at_times: list[str] = []
def fake_post_json_with_retry(endpoint: str, request_preview: dict[str, object]):
live_profile = request_preview.get("live_sampling_request_profile") or {}
body = live_profile.get("body") if isinstance(live_profile, dict) else {}
at_time = body.get("AtTime") if isinstance(body, dict) else {}
std_time = at_time.get("StdTime") if isinstance(at_time, dict) else None
captured_at_times.append(std_time)
return {"Status": "Pass", "Payload": {"SearchEvents": []}}, 1, []
with mock.patch.dict(
vedastro_service_adapter.os.environ,
{
"VEDASTRO_API_ENDPOINT": "https://api.vedastro.org/api",
"VEDASTRO_ENABLE_NETWORK": "1",
},
clear=False,
), mock.patch.object(
vedastro_service_adapter,
"_iter_sample_dates",
return_value=["2026-01-01", "2026-03-01"],
), mock.patch.object(
vedastro_service_adapter,
"_post_json_with_retry",
side_effect=fake_post_json_with_retry,
):
vedastro_service_adapter._run_range_scan_case( # noqa: SLF001
case,
"wealth",
"2026-01-01",
"2026-12-31",
)
assert captured_at_times == [
"12:00 01/01/2026 +08:00",
"12:00 01/03/2026 +08:00",
]
@@ -55,6 +55,11 @@ def test_vedastro_service_adapter_executor_schema_is_declared() -> None:
def test_vedastro_service_adapter_returns_controlled_unconfigured_status() -> None:
env = os.environ.copy()
env.pop("VEDASTRO_API_ENDPOINT", None)
env.pop("VEDASTRO_ENABLE_NETWORK", None)
env["JYOTISH_SKIP_LOCAL_ENV"] = "1"
completed = subprocess.run(
[sys.executable, "scripts/vedastro_service_adapter.py", "--case", "beijing_first_use_demo"],
cwd=ROOT,
@@ -62,6 +67,7 @@ def test_vedastro_service_adapter_returns_controlled_unconfigured_status() -> No
capture_output=True,
timeout=120,
check=False,
env=env,
)
assert completed.returncode == 0, completed.stderr or completed.stdout
@@ -76,6 +82,11 @@ def test_vedastro_service_adapter_returns_controlled_unconfigured_status() -> No
def test_ephemeris_adapter_contract_can_call_vedastro_candidate_stub() -> None:
env = os.environ.copy()
env.pop("VEDASTRO_API_ENDPOINT", None)
env.pop("VEDASTRO_ENABLE_NETWORK", None)
env["JYOTISH_SKIP_LOCAL_ENV"] = "1"
completed = subprocess.run(
[sys.executable, "scripts/ephemeris_adapter_contract.py", "vedastro_service_adapter_candidate"],
cwd=ROOT,
@@ -83,6 +94,7 @@ def test_ephemeris_adapter_contract_can_call_vedastro_candidate_stub() -> None:
capture_output=True,
timeout=120,
check=False,
env=env,
)
assert completed.returncode == 0, completed.stderr or completed.stdout
@@ -96,6 +108,8 @@ def test_ephemeris_adapter_contract_can_call_vedastro_candidate_stub() -> None:
def test_vedastro_service_adapter_builds_request_preview_before_real_network_use() -> None:
env = os.environ.copy()
env["VEDASTRO_API_ENDPOINT"] = "https://example.invalid/vedastro"
env.pop("VEDASTRO_ENABLE_NETWORK", None)
env["JYOTISH_SKIP_LOCAL_ENV"] = "1"
completed = subprocess.run(
[sys.executable, "scripts/vedastro_service_adapter.py", "--case", "beijing_first_use_demo"],
@@ -120,6 +134,8 @@ def test_vedastro_service_adapter_builds_request_preview_before_real_network_use
def test_vedastro_service_adapter_builds_range_scan_preview_before_real_network_use() -> None:
env = os.environ.copy()
env["VEDASTRO_API_ENDPOINT"] = "https://example.invalid/vedastro"
env.pop("VEDASTRO_ENABLE_NETWORK", None)
env["JYOTISH_SKIP_LOCAL_ENV"] = "1"
completed = subprocess.run(
[
@@ -160,6 +176,7 @@ def test_vedastro_range_scan_unconfigured_still_returns_official_search_events_p
env = os.environ.copy()
env.pop("VEDASTRO_API_ENDPOINT", None)
env.pop("VEDASTRO_ENABLE_NETWORK", None)
env["JYOTISH_SKIP_LOCAL_ENV"] = "1"
completed = subprocess.run(
[
@@ -198,6 +215,7 @@ def test_vedastro_range_scan_unconfigured_still_returns_official_search_events_p
assert report["request_preview"]["official_request_profile"]["body"]["StartTime"]["StdTime"] == "12:00 01/01/2026 +08:00"
assert report["request_preview"]["official_request_profile"]["body"]["EndTime"]["StdTime"] == "12:00 01/01/2031 +08:00"
assert report["request_preview"]["official_request_profile"]["body"]["PrecisionHours"] == 100
assert report["request_preview"]["live_sampling_request_profile"]["body"]["AtTime"]["StdTime"] == "12:00 01/01/2026 +08:00"
def test_vedastro_schema_declares_official_search_events_live_contract() -> None:
@@ -364,6 +382,8 @@ def test_vedastro_service_adapter_can_normalize_mock_http_response() -> None:
def test_vedastro_service_adapter_can_normalize_mock_range_scan_response() -> None:
seen_at_times: list[str] = []
class Handler(BaseHTTPRequestHandler):
def do_POST(self) -> None: # noqa: N802
length = int(self.headers.get("Content-Length", "0"))
@@ -371,9 +391,7 @@ def test_vedastro_service_adapter_can_normalize_mock_range_scan_response() -> No
assert payload["Ayanamsa"] == "lahiri"
assert payload["EventTagList"] == ["Marriage", "Personal", "General"]
assert payload["BirthTime"]["StdTime"] == "12:00 01/01/1990 +08:00"
assert payload["StartTime"]["StdTime"] == "12:00 01/01/2026 +08:00"
assert payload["EndTime"]["StdTime"] == "12:00 01/01/2031 +08:00"
assert payload["PrecisionHours"] == 100
seen_at_times.append(payload["AtTime"]["StdTime"])
response = {
"events": [
{
@@ -458,6 +476,10 @@ def test_vedastro_service_adapter_can_normalize_mock_range_scan_response() -> No
assert report["evidence_ledger"][0]["score"] == 72
assert report["evidence_ledger"][0]["raw"]["name"] == "Jupiter supports marriage axis"
assert report["source_metadata"]["endpoint"].startswith("http://127.0.0.1:")
assert report["source_metadata"]["sampling_mode"] == "at_time_sweep"
assert report["source_metadata"]["sample_count"] >= 2
assert seen_at_times[0] == "12:00 01/01/2026 +08:00"
assert len(set(seen_at_times)) >= 2
def test_vedastro_range_scan_records_hashes_and_artifact_path() -> None:
@@ -536,7 +558,9 @@ def test_vedastro_range_scan_records_hashes_and_artifact_path() -> None:
assert metadata["allowlist_domain"] == "marriage"
assert metadata["allowlist_event_count"] == 1
assert metadata["filtered_event_count"] == 1
assert metadata["attempt_count"] == 1
assert metadata["attempt_count"] >= 1
assert metadata["sample_count"] >= 1
assert metadata["sampling_mode"] == "at_time_sweep"
artifact_path = ROOT / metadata["artifact_path"]
assert artifact_path.exists()
artifact = json.loads(artifact_path.read_text(encoding="utf-8"))
@@ -615,8 +639,9 @@ def test_vedastro_range_scan_retries_transient_http_error() -> None:
report = json.loads(completed.stdout)
assert report["status"] == "ok"
assert report["event_count"] == 1
assert report["source_metadata"]["attempt_count"] == 2
assert report["source_metadata"]["retry_error_codes"] == [503]
assert report["source_metadata"]["attempt_count"] >= 2
assert 503 in report["source_metadata"]["retry_error_codes"]
assert report["source_metadata"]["sampling_mode"] == "at_time_sweep"
def test_vedastro_service_adapter_applies_domain_allowlist_to_range_scan_noise() -> None: