Wire VedAstro orchestrator into strict workflows
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user